看一下board/smsk2410/u-boot.lds這個鏈接腳本,可以知道目標程序的各部分鏈接順序。第一個要鏈接的是cpu/arm920t/start.o,那麼U-Boot的入口指令一定位於這個程序中。下面詳細分析一下程序跳轉和函數的調用關係以及函數實現。
6.3.4 U-Boot與內核的關係
U-Boot作爲Bootloader,具備多種引導內核啓動的方式。常用的go和bootm命令可以直接引導內核映像啓動。U-Boot與內核的關係主要是內核啓動過程中參數的傳遞。
1.go命令的實現
/* common/cmd_boot.c */
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong addr, rc;
int rcode = 0;
if (argc < 2) {
printf ("Usage:/n%s/n", cmdtp->usage);
return 1;
}
addr = simple_strtoul(argv[1], NULL, 16);
printf ("## Starting application at 0x%08lX .../n", addr);
/*
* pass address parameter as argv[0] (aka command name),
* and all remaining args
*/
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);
if (rc != 0) rcode = 1;
printf ("## Application terminated, rc = 0x%lX/n", rc);
return rcode;
}
go命令調用do_go()函數,跳轉到某個地址執行的。如果在這個地址準備好了自引導的內核映像,就可以啓動了。儘管go命令可以帶變參,實際使用時一般不用來傳遞參數。
2.bootm命令的實現
/* common/cmd_bootm.c */
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;
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
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);
/* Copy header so we can blank CRC field for re-calculation */
memmove (&header, (char *)addr, sizeof(image_header_t));
if (ntohl(hdr->ih_magic) != IH_MAGIC)
{
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, (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);
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
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;
……
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
……
}
bootm命令調用do_bootm函數。這個函數專門用來引導各種操作系統映像,可以支持引導Linux、vxWorks、QNX等操作系統。引導Linux的時候,調用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)
{
DECLARE_GLOBAL_DATA_PTR;
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
/* Check if there is an initrd image */
if(argc >= 3) {
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16);
printf ("## Loading Ramdisk Image at %08lx .../n", addr);
/* Copy header so we can blank CRC field for re-calculation */
memcpy (&header, (char *) addr, sizeof (image_header_t));
if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number/n");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if(crc32 (0, (char *) data, len) != checksum) {
printf ("Bad Header Checksum/n");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
if(verify) {
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC/n");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK/n");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) {
printf ("No Linux ARM Ramdisk Image/n");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
/* Now check if we have a multifile image */
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/* no initrd image */
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
if (data) {
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
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 (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
printf ("/nStarting kernel .../n/n");
cleanup_before_linux ();
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
do_bootm_linux()函數是專門引導Linux映像的函數,它還可以處理ramdisk文件系統的映像。這裏引導的內核映像和ramdisk映像,必須是U-Boot格式的。U-Boot格式的映像可以通過mkimage工具來轉換,其中包含了U-Boot可以識別的符號。
在命令行提示符下,可以輸入U-Boot的命令並執行。U-Boot可以支持幾十個常用命令,通過這些命令,可以對開發板進行調試,可以引導Linux內核,還可以擦寫Flash完成系統部署等功能。掌握這些命令的使用,才能夠順利地進行嵌入式系統的開發。
輸入help命令,可以得到當前U-Boot的所有命令列表。每一條命令後面是簡單的命令說明。
=> help
? - alias for 'help'
autoscr - run script from memory
base - print or set address offset
bdinfo - print Board Info structure
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootm - boot application image from memory
bootp - boot image via network using BootP/TFTP protocol
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
dhcp - invoke DHCP client to obtain IP/boot params
echo - echo args to console
erase - erase FLASH memory
flinfo - print FLASH memory information
go - start application at address 'addr'
help - print online help
iminfo - print header information for application image
imls - list all images found in flash
itest - return true/false on integer compare
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loop - infinite loop on address range
md - memory display
mm - memory modify (auto-incrementing)
mtest - simple RAM test
mw - memory write (fill)
nfs - boot image via network using NFS protocol
nm - memory modify (constant address)
printenv - print environment variables
protect - enable or disable FLASH write protection
rarpboot - boot image via network using RARP/TFTP protocol
reset - Perform RESET of the CPU
run - run commands in an environment variable
saveenv - save environment variables to persistent storage
setenv - set environment variables
sleep - delay execution for some time
tftpboot - boot image via network using TFTP protocol
version - print monitor version
=>
U-Boot還提供了更加詳細的命令幫助,通過help命令還可以查看每個命令的參數說明。由於開發過程的需要,有必要先把U-Boot命令的用法弄清楚。接下來,根據每一條命令的幫助信息,解釋一下這些命令的功能和參數。
=> help bootm
bootm [addr [arg ...]]
- boot application image stored in memory
passing arguments 'arg ...'; when booting a Linux kernel,
'arg' can be the address of an initrd image
bootm命令可以引導啓動存儲在內存中的程序映像。這些內存包括RAM和可以永久保存的Flash。
第1個參數addr是程序映像的地址,這個程序映像必須轉換成U-Boot的格式。
第2個參數對於引導Linux內核有用,通常作爲U-Boot格式的RAMDISK映像存儲地址;也可以是傳遞給Linux內核的參數(缺省情況下傳遞bootargs環境變量給內核)。
=> help bootp
bootp [loadAddress] [bootfilename]
bootp命令通過bootp請求,要求DHCP服務器分配IP地址,然後通過TFTP協議下載指定的文件到內存。
第1個參數是下載文件存放的內存地址。
第2個參數是要下載的文件名稱,這個文件應該在開發主機上準備好。
=> help cmp
cmp [.b, .w, .l] addr1 addr2 count
- compare memory
cmp命令可以比較2塊內存中的內容。.b以字節爲單位;.w以字爲單位;.l以長字爲單位。注意:cmp.b中間不能保留空格,需要連續敲入命令。
第1個參數addr1是第一塊內存的起始地址。
第2個參數addr2是第二塊內存的起始地址。
第3個參數count是要比較的數目,單位按照字節、字或者長字。
=> help cp
cp [.b, .w, .l] source target count
- copy memory
cp命令可以在內存中複製數據塊,包括對Flash的讀寫操作。
第1個參數source是要複製的數據塊起始地址。
第2個參數target是數據塊要複製到的地址。這個地址如果在Flash中,那麼會直接調用寫Flash的函數操作。所以U-Boot寫Flash就使用這個命令,當然需要先把對應Flash區域擦乾淨。
第3個參數count是要複製的數目,根據cp.b cp.w cp.l分別以字節、字、長字爲單位。
=> help crc32
crc32 address count [addr]
- compute CRC32 checksum [save at addr]
crc32命令可以計算存儲數據的校驗和。
第1個參數address是需要校驗的數據起始地址。
第2個參數count是要校驗的數據字節數。
第3個參數addr用來指定保存結果的地址。
=> help echo
echo [args..]
- echo args to console; /c suppresses newline
echo命令回顯參數。
=> help erase
erase start end
- erase FLASH from addr 'start' to addr 'end'
erase N:SF[-SL]
- erase sectors SF-SL in FLASH bank # N
erase bank N
- erase FLASH bank # N
erase all
- erase all FLASH banks
erase命令可以擦Flash。
參數必須指定Flash擦除的範圍。
按照起始地址和結束地址,start必須是擦除塊的起始地址;end必須是擦除末尾塊的結束地址。這種方式最常用。舉例說明:擦除0x20000 – 0x3ffff區域命令爲erase 20000 3ffff。
按照組和扇區,N表示Flash的組號,SF表示擦除起始扇區號,SL表示擦除結束扇區號。另外,還可以擦除整個組,擦除組號爲N的整個Flash組。擦除全部Flash只要給出一個all的參數即可。
=> help flinfo
flinfo
- print information for all FLASH memory banks
flinfo N
- print information for FLASH memory bank # N
flinfo命令打印全部Flash組的信息,也可以只打印其中某個組。一般嵌入式系統的Flash只有一個組。
=> help go
go addr [arg ...]
- start application at address 'addr'
passing 'arg' as arguments
go命令可以執行應用程序。
第1個參數是要執行程序的入口地址。
第2個可選參數是傳遞給程序的參數,可以不用。
=> help iminfo
iminfo addr [addr ...]
- print header information for application image starting at
address 'addr' in memory; this includes verification of the
image contents (magic number, header and payload checksums)
iminfo可以打印程序映像的開頭信息,包含了映像內容的校驗(序列號、頭和校驗和)。
第1個參數指定映像的起始地址。
可選的參數是指定更多的映像地址。
=> help loadb
loadb [ off ] [ baud ]
- load binary file over serial line with offset 'off' and baudrate 'baud'
loadb命令可以通過串口線下載二進制格式文件。
=> help loads
loads [ off ]
- load S-Record file over serial line with offset 'off'
loads命令可以通過串口線下載S-Record格式文件。
=> help mw
mw [.b, .w, .l] address value [count]
- write memory
mw命令可以按照字節、字、長字寫內存,.b .w .l的用法與cp命令相同。
第1個參數address是要寫的內存地址。
第2個參數value是要寫的值。
第3個可選參數count是要寫單位值的數目。
=> help nfs
nfs [loadAddress] [host ip addr:bootfilename]
nfs命令可以使用NFS網絡協議通過網絡啓動映像。
=> help nm
nm [.b, .w, .l] address
- memory modify, read and keep address
nm命令可以修改內存,可以按照字節、字、長字操作。
參數address是要讀出並且修改的內存地址。
=> help printenv
printenv
- print values of all environment variables
printenv name ...
- print value of environment variable 'name'
printenv命令打印環境變量。
可以打印全部環境變量,也可以只打印參數中列出的環境變量。
=> help protect
protect on start end
- protect Flash from addr 'start' to addr 'end'
protect on N:SF[-SL]
- protect sectors SF-SL in Flash bank # N
protect on bank N
- protect Flash bank # N
protect on all
- protect all Flash banks
protect off start end
- make Flash from addr 'start' to addr 'end' writable
protect off N:SF[-SL]
- make sectors SF-SL writable in Flash bank # N
protect off bank N
- make Flash bank # N writable
protect off all
- make all Flash banks writable
protect命令是對Flash寫保護的操作,可以使能和解除寫保護。
第1個參數on代表使能寫保護;off代表解除寫保護。
第2、3參數是指定Flash寫保護操作範圍,跟擦除的方式相同。
=> help rarpboot
rarpboot [loadAddress] [bootfilename]
rarboot命令可以使用TFTP協議通過網絡啓動映像。也就是把指定的文件下載到指定地址,然後執行。
第1個參數是映像文件下載到的內存地址。
第2個參數是要下載執行的映像文件。
=> help run
run var [...]
- run the commands in the environment variable(s) 'var'
run命令可以執行環境變量中的命令,後面參數可以跟幾個環境變量名。
=> help setenv
setenv name value ...
- set environment variable 'name' to 'value ...'
setenv name
- delete environment variable 'name'
setenv命令可以設置環境變量。
第1個參數是環境變量的名稱。
第2個參數是要設置的值,如果沒有第2個參數,表示刪除這個環境變量。
=> help sleep
sleep N
- delay execution for N seconds (N is _decimal_ !!!)
sleep命令可以延遲N秒鐘執行,N爲十進制數。
=> help tftpboot
tftpboot [loadAddress] [bootfilename]
tftpboot命令可以使用TFTP協議通過網絡下載文件。按照二進制文件格式下載。另外使用這個命令,必須配置好相關的環境變量。例如serverip和ipaddr。
第1個參數loadAddress是下載到的內存地址。
第2個參數是要下載的文件名稱,必須放在TFTP服務器相應的目錄下。
這些U-Boot命令爲嵌入式系統提供了豐富的開發和調試功能。在Linux內核啓動和調試過程中,都可以用到U-Boot的命令。但是一般情況下,不需要使用全部命令。比如已經支持以太網接口,可以通過tftpboot命令來下載文件,那麼還有必要使用串口下載的loadb嗎?反過來,如果開發板需要特殊的調試功能,也可以添加新的命令。
在建立交叉開發環境和調試Linux內核等章節時,在ARM平臺上移植了U-Boot,並且提供了具體U-Boot的操作步驟。
6.4.3 U-Boot的環境變量
有點類似Shell,U-Boot也使用環境變量。可以通過printenv命令查看環境變量的設置。
U-Boot> printenv
bootdelay=3
baudrate=115200
netmask=255.255.0.0
ethaddr=12:34:56:78:90:ab
bootfile=uImage
bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M
bootcmd=tftp 0x30008000 zImage;go 0x30008000
serverip=192.168.1.1
ipaddr=192.168.1.100
stdin=serial
stdout=serial
stderr=serial
Environment size: 337/131068 bytes
U-Boot>
表6.5是常用環境變量的含義解釋。通過printenv命令可以打印出這些變量的值。
表6.5 U-Boot環境變量的解釋說明
環 境 變 量
|
解 釋 說 明
|
bootdelay
|
定義執行自動啓動的等候秒數
|
baudrate
|
定義串口控制檯的波特率
|
netmask
|
定義以太網接口的掩碼
|
ethaddr
|
定義以太網接口的MAC地址
|
bootfile
|
定義缺省的下載文件
|
bootargs
|
定義傳遞給Linux內核的命令行參數
|
bootcmd
|
定義自動啓動時執行的幾條命令
|
serverip
|
定義tftp服務器端的IP地址
|
ipaddr
|
定義本地的IP地址
|
stdin
|
定義標準輸入設備,一般是串口
|
stdout
|
定義標準輸出設備,一般是串口
|
stderr
|
定義標準出錯信息輸出設備,一般是串口
|
U-Boot的環境變量都可以有缺省值,也可以修改並且保存在參數區。U-Boot的參數區一般有EEPROM和Flash兩種設備。
環境變量的設置命令爲setenv,在6.2.2節有命令的解釋。
舉例說明環境變量的使用。
=>setenv serverip 192.168.1.1
=>setenv ipaddr 192.168.1.100
=>setenv rootpath "/usr/local/arm/3.3.2/rootfs"
=>setenv bootargs "root=/dev/nfs rw nfsroot=/$(serverip):/$(rootpath) ip=
/$(ipaddr) "
=>setenv kernel_addr 30000000
=>setenv nfscmd "tftp /$(kernel_addr) uImage; bootm /$(kernel_addr) "
=>run nfscmd
上面定義的環境變量有serverip ipaddr rootpath bootargs kernel_addr。環境變量bootargs中還使用了環境變量,bootargs定義命令行參數,通過bootm命令傳遞給內核。環境變量nfscmd中也使用了環境變量,功能是把uImage下載到指定的地址並且引導起來。可以通過run命令執行nfscmd腳本。