uboot入口
在PC机上引导程序一般由BIOS开始执行,然后读取硬盘中位于MBR(Main Boot Record,主引导记录)中的Bootloader(例如LILO或GRUB),并进一步引导操作系统的启动。然而在嵌入式系统中通常没有像BIOS那样的固件程序,因此整个系统的加载启动就完全由bootloader来完成。它主要的功能是加载与引导内核映像 。
主引导记录(MBR,Master Boot Record)是计算机开机以后访问硬盘时所必须要读取的第一个扇区,位于硬盘的第0柱面第0磁道第一扇区,大小为512字节。
主引导程序的任务就是查找并且加载处在硬盘分区上的次引导程序。通过分区表查找活动分区,并将处在活动分区的次引导加载程序读取到内存里面运行。
MBR的BootLoader中安装有GRUB程序的一部分,因为BootLoader只有446Bytes,容量太小,所以只安装了GRUB中一个小的程序在里面,用于加载剩余的GRUB程序。
uboot的入口函数
同大多数的Bootloader一样,uboot的启动过程也分为BL1、BL2两个阶段,分别对应着SPL和Uboot。
- SPL(BL1阶段):负责开发板的基础配置和设备初始化,并且搬运Uboot到内存中,由汇编代码和少量的C语言实现
- Uboot(BL2阶段):主要负责初始化外部设备,引导Kernel启动,由纯C语言实现。
- u-boot.lds: uboot的连接脚本,决定uboot的入口,由uboot中的makefile编译生成,可以在编译(make)后,根据编译信息找到其生成的具体位置;
- 连接脚本的宏定义在linkage.h,位于include\linux中;
- 根据1中的程序入口找到程序入口,根据u-boot.lds可知(arch/arm/cpu/armv7/start.o(,text*)),即该目录侠的start.s文件;
- 进入main,位于arch/arm/lib中俄crt0.s,在crt0.s中可以看到调用DeC函数board_init_f和board_init_r接口。
- U-Boot启动完成后,最终进入到main_loop()循环中。若在bootdelay倒计时为0以前,U-Boot控制台有输入,则进入命令解析-执行的循环;若控制台无输入,U-Boot将启动内核。
- U-Boot启动内核可归结为如下四个步骤:
1)将内核搬移至DDR中;数据结构
2)校验内核格式、CRC;ide
3)准备传参;函数
4)跳转执行内核。
我们熟悉的u-boot启动的时候执行的一段程序,这段程序一般存放在Nand flash中或Nor flash中。我们所说的Nand flash启动或Nor flash启动主要是涉及到一段搬移代码。这段搬移代码的功能是u-boot自己把自己搬移到内存中执行。如下是Nor flash启动中的这段搬移代码(这里以s3c2410为例)
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
#endif
上面这段代码就是把u-boot搬移到内存。而不同的启动方式区别也就在这段代码上,如果我们这里是Nand flash启动的话我们也需要写相同功能的代码,不同的是对于Nand的操作和Nor的操作是完全不同的,选择Nor flash启动是将Nor flash映射到片选0上也就是0x0地址而选择Nand flash启动则是将CPU的片内RAM(4K)映射到0地址,通过Nand flash控制器操作Nand flash。我们这里讨论如何实现Nand 和 Nor双启动。下面我们看看这两种启动的映射关系。
uboot启动linux的过程
linux内核镜像常见到的有两种形式,zImage和uImage。这两种文件的格式稍有差别,所以启动这两种格式的内核镜像也会有所不同。目前,uboot只支持启动uImage类型的镜像,对zImage还不支持(但是可以移植,TQ2440就是这样做的)。
uImage和zImage
zImage是用命令“#make zImage”生成的,我截取了生成信息最后部分的内容如下:
OBJCOPY arch/arm/boot/Image Kernel: arch/arm/boot/Image is ready GZIP arch/arm/boot/compressed/piggy.gz AS arch/arm/boot/compressed/piggy.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready
从中可以看到,zImage是经过gzip压缩过的,所以在内核启动过程(不属于u-boot控制范围,在内核镜像的头部嵌有解压函数)中必然会对应一个解压过程。
uImage是u-boot专用的内核镜像,可用命令“#make uImage”生成。生成信息最后部分的内容如下:
Kernel: arch/arm/boot/Image is ready Kernel: arch/arm/boot/zImage is ready UIMAGE arch/arm/boot/uImage Image Name: Linux-2.6.30.4-EmbedSky Created: Thu Mar 20 19:53:32 2014 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2314736 Bytes = 2260.48 kB = 2.21 MB Load Address: 0x30008000 Entry Point: 0x30008000 Image arch/arm/boot/uImage is ready
事实上,uImage是调用mkimage(uboot制作的工具)这个工具生成的。
root@daneiqi:/opt/EmbedSky# mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage Image Name: linux-2.6.30 Created: Thu Mar 20 19:59:36 2014 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2314736 Bytes = 2260.48 kB = 2.21 MB Load Address: 0x30008000 Entry Point: 0x30008000
在原来的可执行映象文件zImage的前面加上一个0x40字节的头, 记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的, 哪种类型,加载内存中的哪个位置,入口点在内存的那个位置以及映象名是什么。
头部的结构是在include/image.h中定义的,如下所示:
typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ 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 */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t;
打开上边生成的uImage文件,可以看到对应的数据。
(1)ih_magic 0x27051956 magic值,我觉得是uImage的头部开始值,根据这个值,判断是否是uImage
(2)ih_crc 0x19dbf9c6 头部校验
(3)ih_time 0x74295319 创建时间
(4)ih_size 0x002351f0 镜像大小为2260.48KB
(5)ih_load 0x30008000 内核加载地址
(6)ih_ep 0x30008000 内核运行地址,“theKernel”指向该地址,说明这里藏着进入第一个函数--解压
(7)ih_dcrc 0x38fc654e 内核校验
(8)ih_os 0x05 #define IH_OS_LINUX 5 /* Linux */
(9)ih_arch 0x02 #define IH_CPU_ARM 2 /* ARM */
(10)ih_type 0x02 #define IH_TYPE_KERNEL 2 /* OS Kernel Image */
(11)ih_comp 0x00 #define IH_COMP_NONE 0 /* No Compression Used */
(12)ih_name Linux_2.6.30.4-EmbedSky
u-boot内核启动流程概述
前文已经说明u-boot只支持uImage,步骤三、四都是针对uImage的。
另外声明一点,步骤三四的测试uboot代码是韦东山视频提供的。
1、从NandFlash中读取内核到RAM中
2、在RAM中,给内核进行重定位
3、给内核传递参数
4、启动内核
u-boot启动内核细节分析
启动命令
从环境变量中查看启动命令:
从NandFlash中读取内核到RAM中
nand read.jffs2 0x30007FC0 kernel
此命令会激活(common/cmd_nand.c)中的do_nand函数,从而将nandflash上的kernel分区加载到0x30007fc0位置处。
OpenJTAG> mtd device nand0 <nandflash0>, # parts = 4 #: name size offset mask_flags 0: bootloader 0x00040000 0x00000000 0 1: params 0x00020000 0x00040000 0 2: kernel 0x00200000 0x00060000 0 3: root 0x0fda0000 0x00260000 0 active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000 defaults: mtdids : nand0=nandflash0 mtdparts: mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
从分区表中,可以看出kernel分区的起始地址是0x60000,大小是0x200000(2M),这条命令实际上等效于
nand read.jffs2 0x30007FC0 0x60000 0x200000
也可以使用命令
nand read 0x30007FC0 0x60000 0x200000
nand read.jffs2可以自动页对齐,所以大小可以是非页整的;如果使用nand read的大小必须是页对齐的。
读取uImage头部
bootm 0x30007fc0
此命令会激活(common/cmd_bootm.c)中的do_bootm函数,从而开始执行
2、在RAM中,给内核进行重定位 3、给内核传递参数 4、启动内核
image_header_t header; 定义一个全局变量header,是读取头部的缓冲区
addr = simple_strtoul(argv[1], NULL, 16); 定位头部地址,将字符串“0x30007fc0”转化为整型
printf ("## Booting image at %08lx ...\n", addr); 显示从哪儿启动
memmove (&header, (char *)addr, sizeof(image_header_t)); 读取头部到header变量中
判断当前的内存区是否是uImage的开始位置
if (ntohl(hdr->ih_magic) != IH_MAGIC) { { puts ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-1); return 1; } }
注意到:
#define IH_MAGIC 0x27051956 /* Image Magic Number */(include/image.h)
校验头部
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; }
打印头部信息
/* 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, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-3); return 1; } puts ("OK\n"); } SHOW_BOOT_PROGRESS (4);
注意到data已经跳过了uImage的头部,指向了真正的内核首部,也即0x30008000。
核查架构、内核类型、压缩类型等信息,其中会涉及到重定位
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)
#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
#endif
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-4);
return 1;
}
SHOW_BOOT_PROGRESS (5);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = htonl(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]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
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);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
#ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == data) {
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);
#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;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts();
/* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
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:
/* handled below */
break;
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);
在这部分代码中,有这么一部分关于压缩类型的:
switch (hdr->ih_comp) { case IH_COMP_NONE: if(ntohl(hdr->ih_load) == data) { 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); #endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */ } break;
可以看到,u-boot会判断当前去除uImage头部内核代码所处的位置(7步骤已经说明地址是data)是否与编译时安排的重定位位置(hdr->ih_load)一致。
如果一致,就打印一句话。
如果不一致,则需要调用 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);进行内核的重定位,要知道它有2M多的大小,会花费一些时间。尽量使读取内核的时候,就读取到hdr->ih_load-64的位置上,这样就不必再搬运一次。
根据操作系统类型,启动对应的操作系统
复制代码
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
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_linux,继续启动linux系统
此函数在lib_arm/armlinux.c中
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);
可见,已经将内核运行的首地址赋给了theKernel函数指针变量,将来可以利用这个变量调用进入内核的函数。
另外,在进入内核之前,要给内核传递参数。方法是将参数以一定的结构放在内存指定的位置上,将来内核从该地址读取数据即可。
命令行的启动参数存储在以bootargs命名的对象里,值为
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
告诉内核,启动后的根文件系统位于mtd的哪个区,初始进程,以及控制台.
判断是否是一个ramdisk或者multi镜像
/*
* 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 */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
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, (unsigned 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);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
if (verify) {
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (unsigned 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);
}
#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */
/*
* 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;
}
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
给内核传递参数
#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
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
比较重要的函数有:
setup_start_tag (bd);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);
其中 bd->bi_boot_params(参考uboot全局变量),bi_boot_params=>>0x30000100,启动参数存放的位置。
启动内核
printf ("\nStarting kernel ...\n\n"); theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
把机器码以及启动参数存放的位置都告诉给内核。
启动过程展示
u-boot启动zImage
直接启动zImage
既然,zImage是uImage去除头部的部分,那么可以从0x30008000直接启动zImage,我们用go命令去执行。
可见,内核的第一个函数果然是解压函数。但是程序卡到图片最后的位置,不能继续执行。
原因是由于没有给内核传递启动参数,也就是说在执行函数theKernel之前,没有做好准备
void (*theKernel)(int zero, int arch, uint params);
移植u-boot支持启动zImage
再来看一下启动大纲:
1、从NandFlash中读取内核到RAM中 2、在RAM中,给内核进行重定位 3、给内核传递参数 4、启动内核
可以直接从nandflash中将内核zImage读取到内存0x30008000位置处,然后在0x30000100位置处传递参数,也就是调用函数:
setup_start_tag (bd); setup_memory_tags (bd); setup_commandline_tag (bd, commandline); setup_end_tag (bd);
最后,调用theKernel函数启动内核。
uboot全局变量
global_data(include/asm-arm/global_data.h)
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
include/asm-arm/global_data.h,定义了全局变量指针r8,可以说全局变量区不像普通变量可以用变量名访问,它只能用指针访问
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
r8在start_armboot(lib_arm/board.c)函数的开始处赋值
/* Pointer is writable since we allocated a register for it */ gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
通过计算便能得到r8的值,不同的开发板配置可能不一样,但是算法是一样的,这里算得的结果是0x33f4ffdc。从内存中查看到底存的是什么东东。
1、bd =>>0x33f4ffb8,bd指针
2、flag =>>0x02
3、 baudrate =>>0x0001c200=115200,串口波特率
4、have_console =>>0x01
5、reloc_off =>>0x0
6、env_addr =>>0x33f5a164,环境变量的分配内存地址,这个值在堆区
7、 env_valid =>>0x01,环境变量已经设置完成
8、fb_base =>>0x0,显示器缓冲区
9、jt =>>0x33f61bc0,跳转函数表指针
总共:36 Bytes
bd_info (include/asm-arm/u-boot.h)
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
1、 bi_baudrate=>>0x0001c200,串口波特率
2、 bi_ip_addr=>>0x6e00000a,ip地址
3、 bi_enetaddr=>>0x00000000 、0x00000000
注意:它虽然需要6个字节,但编译器对齐分配的时候取了8个字节
4、bi_env=>>0x0
5、 bi_arch_number=>>0xc1,机器码,对应着MACH_TYPE_S3C2410
6、bi_boot_params=>>0x30000100,启动参数
7、 bi_dram[CONFIG_NR_DRAM_BANKS],DRAM信息
smdk2410中定义CONFIG_NR_DRAM_BANKS为1
1> start=>>0x30000000,DRAM起始地址
2> size=>>0x04000000,DRAM大小
总共:36 Bytes
environment_s(include/environment.h)
typedef struct environment_s { unsigned long crc; /* CRC32 over data bytes */ #ifdef CFG_REDUNDAND_ENVIRONMENT unsigned char flags; /* active/obsolete flags */ #endif unsigned char data[ENV_SIZE]; /* Environment data */ } env_t;
crc
crc校验值如图所示为0x48ec48fd
环境变量打印效果
所有的环境变量打印的效果,打印的顺序是依据其在内存中排放的先后顺序
65532为分配的环境变量区大小,127是当前使用大小
环境变量完整内存
环境变量对应的二进制内存值
结论:
1>一个环境变量以“空”结束
2>每个环境变量等号前边的是其名字,等号后边的是其值(值也是以字符串存储在内存中,计算机真正使用
还需要转换)
3>所有环境变量的最后边以“空”结束
参考文档:https://www.cnblogs.com/amanlikethis/p/3614594.html
uboot全局变量 - amanlikethis - 博客园 (cnblogs.com)
uboot分析:uboot启动内核 - 爱悠闲 (aiuxian.com)
原来Uboot是这样启动的! - 知乎 (zhihu.com)
ubootu3 添加nand flash支持-robinfit01-ChinaUnix博客