简介:
本文主要介绍为JZ2440写BootLoader来引导内核,启动Linux系统。本文为BootLoader的第二阶段,即介绍如何引导内核和启动内核。
声明:
本文主要是看了韦东山老师的视频后,所写的课程总结。希望对你有所帮助。
BootLoader第二阶段代码:
这里我们还要强调一下BootLoader的目的:系统初始化,加载并引导内核程序。而我们在上面一篇文章:
嵌入式Linux——写jz2440BootLoader的第一阶段代码中已经详细的介绍了第一阶段系统的初始化,而在BootLoader的第二阶段主要就是解决下面两个问题,即加载并引导内核。同时我们知道我们在u-boot中会设置一些参数,例如bootargs或者其他的设置。那么我们是通过何种方式让内核知道我们为他传递了参数,同时知道参数是什么。这就需要在内核和BootLoader之间建立一种协议,用这种协议来完成参数的传递。而这种协议我们说的更通俗一点就是一种数据传输的格式即TAG参数。BootLoader将要传递的参数以TAG参数的格式进行编辑,然后传递给内核。设置好参数我们就要跳到内核告诉他参数的位置,并执行内核代码。
通过上面的介绍我们大致可以将第二阶段的代码分为下面几步:
1. 从nand中将内核读到SDRAM中
2. 设置TAG参数
3. 跳转到内核,执行内核代码
而完成上面几步后程序就进入kernel程序中运行了。而这时我们BootLoader的使命就算完成了。同时BootLoader程序也就没有任何作用了。
1. 从nand中将内核读到SDRAM中
下面我们详细讲解上面的几步,来了解我们是如何进入内核函数的。而在写这些之前我希望大家对内核有些了解,因为只有我们了解了下面的接口信息,我们上面的接口程序才好写。而那些内核信息是需要我们了解的,我会在后面的文中提及。下面我们还是看上面的步骤1,同样又是要转移数据,那么就逃脱不了我们数据传输的三要素即,源,目的,长度。既然是将内核从nand读到SDRAM,那么我们的源和目的就有了,即源是nand,而目的为SDRAM,而具体的源与目的的地址我们就要看在nand中和SDRAM中的内存分配情况了。
我们先看在nand中的分配情况,即看源的具体地址。如下图中红 {MOD}方框所示:
我们将内核的映像文件uImage放到了上图中红 {MOD}方框圈出空间,在这里开始地址为0x60000,那么我们的源地址本应该是在0x60000这个位置上。但是我们要注意的是在0x60000处存放的是kernel的映像文件uImage,而不是kernel本身。所以我们要从真正kernel的位置拷贝数据。而kernel真正开始的地方是在uImage开始地址后64字节处,即0x00060000+64处。而为什么是64字节,这就要看uImage的文件了,如下图:
从上面我们知道uImage由64字节头部和kernel的组成。而64字节的头部就包括下面的内容:
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;
其中最重要的是ih_size和ih_load两个参数,第一个是映像文件的大小,而另一个是映像文件的加载地址。这里我们就要引入在上面讨论的数据转移的目的地址了。这个加载地址就是我们的目的地址。而我们这个时候就需要了解一些内核的知识了,因为这里的内核我们在u-boot中可以任意放置,但是在BootLoader中我们必须要放到规定的位置,这是为什么那?是因为在u-boot中有memmove函数来将kernel文件移动到正确的位置。而在BootLoader中 我们并没有这个函数,所以我们只能规规矩矩的将这个内核文件放到内核规定的位置。而内核规定的位置是哪里哪?这就要看内核文件了,我们在内核中有如下代码:
#ifdef CONFIG_CPU_S3C2400
#define PHYS_OFFSET UL(0x0C000000)
#else
#define PHYS_OFFSET UL(0x30000000)
#endif
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
/*
* swapper_pg_dir is the virtual address of the initial page table.
* We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we must
* make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
* the least significant 16 bits to be 0x8000, but we could probably
* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
*/
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif
而通过上面的信息我们确定了要将内核加载到SDRAM中的0x30008000的地方。而第三个问题加载长度的问题,我们可以读取uImage的头部中ih_size参数来确定映像文件的大小,进而确定要传输数据的大小。但是我们这里就不这么做了,我们直接读取nand中为uImage分配的空间大小就可以了。
所以从nand中将内核读到SDRAM中代码为:
//将内核从nand flash中拷贝到SDRAM中
nand_read(0x00060000+64,0x30007fc0+64,0x200000);
2. 设置TAG参数
我们知道kernel在启动的过程中要读取一些BootLoader为其传递的参数来设置kernel的启动参数,而这些参数都是什么,他们以一种什么样的方式传递到kernel中,这些都是我们要讨论的。我们知道BootLoader为内核传递参数是有一定的规律性的,而不是想怎么传就怎么传,这样就不符合内核的思想了。而内核的思想是模块化,同时也便于移植,所以他就有一定的防范,而这个规范就是TAG参数,BootLoader与内核之间约定用过这种方式来传递参数,即BootLoader通过TAG参数的方式将要传输的数据放到指定的地址中,而内核则通过TAG参数的方式将存放在指定地点中的数据读出。这就体现了TAG的参数的重要性了。
同时为了便于区分和识别,TAG参数也有他的开始TAG和结束TAG,而在开始和结束TAG之间的就是我们要传递的参数了。我在下面将几个重要的TAG参数画出,如下图:
从图中我们可以看出这里主要分为4个TAG,他们分别为start_tag,memory_tag,comandline_tag,end_tag。我们在前面已经说了start_tag和end_tag分别表示了TAG参数的开始和结束,所以这里我们不细分析这两个参数,我只想强调一点的是在start_tag中标明了TAG参数的开始地址。接下来我们看memory_tag和commandline_tag,memory_tag的作用是向内核传递SDRAM的起始地址以及大小,而commandline_tag的作用是向内核传递命令行参数。
在memory_tag中,我们向内核传递了SDRAM的起始地址为0x30000000,SDRAM的大小为0x400000。
在commandline_tag中我们将命令行参数"console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2"传递到内核中。
所以设置内核参数的代码为:
//设置TAG参数
setup_start_tag();
setup_memory_tags();
setup_commandline_tag("console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2");
setup_end_tag();
struct tag *params;
static void setup_start_tag (void)
{
params = (struct tag *) 0x30000100;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
static void setup_memory_tags (void)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 0x400000;
params = tag_next (params);
}
int strlen(char *p)
{
int i=0;
while(*p != '