尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。
看一下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命令的实现
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 0xlX .../n", addr);
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命令的实现
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 lx .../n", addr);
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);
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:
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函数的实现
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);
if(argc >= 3) {
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16);
printf ("## Loading Ramdisk Image at lx .../n", addr);
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);
}
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
data = (ulong) (&len_ptr[2]);
for (i = 1; len_ptr[i]; ++i)
data += 4;
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
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 lx) .../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
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脚本。