嵌入式Linux引导过程之1.6——Xloader的Xloader_Entry
--by FeCen
我们已经看完了XLOADER_ENTRY里调用的前两个标号的代码,分别是sys_init和ddr_init。对于一个嵌入式系统来说,这两个部分的代码是在一开始就执行的,至少是在从bootrom里面的firmware出来之后最初执行的代码,也是我们自己能够控制的最初的代码(在bootrom里面的firmware是在芯片出厂的时候就固化在芯片里面的了,一般情况下,我们是无法改变里面的代码的)。
正因为如此,对于sys_init和ddr_init来说,需要做很多与系统底层硬件相关的初始化工作。而对于这种工作来说,汇编代码无疑是最能够胜任得了,因此,sys_init和ddr_init这两个代码块都是用ARM的汇编写成的。不过对于代码的阅读者来说,至少对我来说,阅读汇编还是比较痛苦的,毕竟习惯了c程序那样的高级语言,在.S的汇编世界里还是非常挣扎的。
不过当我们走过了那两道坎之后,终于看到了一丝光明,c终于出现了!c的出现,不仅意味着我们将来阅读代码更加舒服了,更重要的是,c的出现意味着最底层的需要用汇编才能够完成的初始化工作基本已经告一段落了,接下来需要处理的过程,更多的是与逻辑相关,而与硬件多少关系少一点了。这对于我这种不懂硬件的人来说,无疑是一个值得鼓舞的消息。虽然在后面的过程中多多少少还会遇到与硬件打交道的汇编代码,不过谁的人生能够一路坦途呢,没有那么一点的跌宕起伏的人生反而会显得缺少点什么。程序也是一样,玩的就是刺激。
呵呵,好了,那么多废话,该切入正题了。先来回忆一下在XLOADER_ENTRY中,调用完了sys_init和ddr_init之后调用了一个叫做Xloader_Entry的函数,而且我们说过,XLOADER_ENTRY在调用完Xloader_Entry之后就再也不会回到XLOADER_ENTRY中了,也就是说,程序走到这里的时候,未来怎么走,都由Xloader_Entry说了算。是死是活都已经不关XLOADER_ENTRY的事了,它也管不着。
好,现在来贴Xloader_Entry的代码,该函数位于一个我们熟悉的源文件中,叫做main.c,呵呵,似乎很早以前自己写程序玩的时候都会把它叫做main.c,然后里面只有一个函数叫做main,然后main函数就是整个程序的入口函数,程序执行的第一个语句就是main函数里面的第一个语句。。。哈哈,怀念啊。。。
不过现在给自己提一个醒,在一个还没有操作系统支持的系统中,在它上电之后,它在执行语句的时候,是不会管这个语句对应的函数是不是叫作main的,它只管执行目前PC(Program Counter)寄存器指向的地址处的指令。因此,在看与系统底层相关的代码的时候,一定要注意看一看以下几个文件:.lds文件,.map文件,Makefile以及.code等等,往往这些文件能够给我们一些启示,然后我们就可以顺着这个线索一步一步捋程序了。
来看代码:
36 void Xloader_Entry(void)
37 {
38 int ret;
39 unsigned int loadaddress;
40 unsigned int data_size;
41 image_header_t *hdr = &header;
42
43 int i;
44
45 i = sizeof(t_flash_device);
46 if (i == 0) i =10;
47 else i = 15;
48
49 /* Authenticate Uboot and copy it in DDR */
50 ret = authenticate_uboot();
51 if( ret == 0)
52 {
53 /* UBoot is not there in flash so nothing to do */
54 // while(1);
55 }
56 else
57 {
58 // Program_Interconnection_Matrix();
59 /* Copy uboot from Flash to DDR*/
60 loadaddress = ntohl(hdr->ih_load);
61 data_size = ntohl(hdr->ih_size);
62 memcpy(loadaddress, (UBOOT_BASE_ADDRESS + 64),data_size);
63
64 /* @CHANGE: added by Subhash */
65 RunUBoot(loadaddress);
66
67 #if 0
68 /* Jump to uboot if UBOOT is to be xecuted by ARM1 */
69 #if defined(EXECUTE_UBOOT_ON_ARM1)
70 Program_Interconnection_Matrix();
71 Jump_To_UBoot(loadaddress);
72 while(1); /* will never come here */
73 #else
74 /* tell ARM2 to execute the UBoot, at this time ARM1 will be doing nothing */
75 Program_Interconnection_Matrix();
76 setup_branch(loadaddress);
77 Enable_ARM2_Core();
78 while(1);
79 #endif
80 #endif // #if 0
81 }
82
83 /* UBoot not found in NOR so authenticate it in NAND flash */
84 /* Authenticate Uboot in NAND Flash and copy it in DDR */
85 splus_nand_boot();
86
87 /* wait for infinite if both NAND & NOR boot fails */
88 while(1);
89 }
90
首先我们看到以下这个定义:
41 image_header_t *hdr = &header;
这边的这个header就是我们在上文中所说的由mkimage这个工具程序添加在目标文件之前的那个image header。
那为什么会在Xloader的源文件中出现这样一个header呢?其实很好理解,因为Xloader的功能是做一些初始化的工作(包括对系统及外设,主要是时钟,进行初始化,对SDRAM进行初始化,还有就是还没有讲的验证uboot以及将uboot加载到SDRAM指定的地址中)。在完成这些初始化工作之后,就将控制权交给uboot了。我们看到在xloader所做的所有工作中,有一个验证uboot的工作,也就是看一看在指定的flash中有没有uboot image的存在,如果存在才能够继续往下走啊对吧。那么xloader是如何完成这个工作的呢?跟我们上一篇文章中分析的BootRom的做法一样,它会去查看一个在flash的某一个地方是不是存在一个uboot image header,如果存在这样一个header就说明存在整个image。
因此,在Xloader中出现的这个header其实是对应于uboot image header的,用来验证在flash中是否存在有uboot image。
查看源代码,可以发现,header变量定义在xloader根目录下的xloader/xloader.c中。具体是这么定义的:
4 image_header_t header;
进一步的,image_header_t定义在xloader根目录下的include/image.h中,它的定义如下:
4 #define IH_NMLEN 32 /* Image Name Length */
24 typedef struct image_header {
25 uint32_t ih_magic; /* Image Header Magic Number */
26 uint32_t ih_hcrc; /* Image Header CRC Checksum */
27 uint32_t ih_time; /* Image Creation Timestamp */
28 uint32_t ih_size; /* Image Data Size */
29 uint32_t ih_load; /* Data Load Address */
30 uint32_t ih_ep; /* Entry Point Address */
31 uint32_t ih_dcrc; /* Image Data CRC Checksum */
32 uint8_t ih_os; /* Operating System */
33 uint8_t ih_arch; /* CPU architecture */
34 uint8_t ih_type; /* Image Type */
35 uint8_t ih_comp; /* Compression Type */
36 uint8_t ih_name[IH_NMLEN]; /* Image Name */
37 } image_header_t;
这就是传说中的image header的定义了。它贯穿着从BootRom到uboot的整个过程,在BootRom、Xloader以及UBoot中,image header的定义都是它。
到目前为止,我们需要关心的这个结构体里面这几个域,ih_magic、ih_size、ih_load以及ih_name。其中ih_magic和ih_name是对某一个image的标识,在识别image的时候就靠它们了。ih_load就是我们在上一篇文章中提到的那个load address,在使用mkimage制作xloader image或者uboot image的时候,会将Makefile中的TEXT_BASE填充到这个域中。而ih_size就表示image的字节大小,注意这个大小是不包括上述64字节的header部分的,就是说,是header之后跟着的那个elf目标文件的大小。
好了,对header的说明暂且到这里,我们接着往下看。
紧接着看到了那几句对局部变量i的赋值,这几句我睁大了眼睛看了好几遍都没有发现有什么作用,整个函数中,除了这几句赋值语句根本就没有其他什么地方引用过这个i。而这个i又是一个纯粹的局部变量,并且也不是什么寄存器地址,实在看不出它在这里有什么作用。所以我暂且判断这几句为xloader中的垃圾代码,或许代码的原作者在写代码的时候打了个瞌睡,回头就忘了删了,呵呵。或者这部分代码有什么我等小辈不知道的神奇的功效,当我在这里说这是垃圾代码的时候,或许那个被我冤枉为打瞌睡的人正在骂我为垃圾人。不管怎样吧,我没有看懂这部分内容。暂且定以为无效代码吧,日后有新的发现再说。
接着往下看:
49 /* Authenticate Uboot and copy it in DDR */
50 ret = authenticate_uboot();
这里,就是对flash中的uboot进行验证啦。通过一句authenticate_uboot(),如果在flash的某个地方存在我们需要的uboot image的话,那么它将返回1。否则调用该函数就将返回0,说明我们需要的uboot image并不在那里。
对于authenticate_uboot()函数的细节,我们下次再说。这里只需要明白它的作用就可以了,这个作用也是Xloader的一个基本功能。
做完下面的这个if-else,在正常情况下,Xloader也就该功成身退了。粗粗一看这个if-else似乎是有不少代码,但其实在else里面的67-80行部分用#if 0 ... #endif括起来的代码是不会被编译进去的,因此实际上这部份代码是可以删掉的(这部分其实就是RunUBoot函数的代码,可能作者最初没有将这部份代码封装到一个函数中,后来为了看着舒服就简单的封装了一下,说简单是真的简单,就是将原来的代码原封不动的挪到了RunUBoot函数中,使得在Xloader_Entry函数中看起来更加简洁)。作者可能为了将来修改方便就没有删去。在这里,为了看起来清楚一点我把这部分代码去掉:
51 if( ret == 0)
52 {
53 /* UBoot is not there in flash so nothing to do */
54 // while(1);
55 }
56 else
57 {
58 // Program_Interconnection_Matrix();
59 /* Copy uboot from Flash to DDR*/
60 loadaddress = ntohl(hdr->ih_load);
61 data_size = ntohl(hdr->ih_size);
62 memcpy(loadaddress, (UBOOT_BASE_ADDRESS + 64),data_size);
63
64 /* @CHANGE: added by Subhash */
65 RunUBoot(loadaddress);
66
81 }
这里的ret就是上面调用authenticate_uboot()的返回值。当ret为0时,说明在指定的flash地址中没有找到uboot image。如果不考虑从NAND flash引导的情况的话,代码走到这里就没有办法继续往下走了,因此作者在最初的时候在这里放了一个while(1)。只不过,后来作者添加了对从NAND flash引导的支持,因此才将这句无限循环给去掉了。从这里我们可以看到,对于这个Xloader来说,uboot image既可以放在NOR flash中,也可以放在NAND flash中进行引导。
如果ret为1,这也就是我所说的正常情况,这时,需要做的工作就是将uboot的代码从flash搬移到指定的SDRAM中,然后跳转到SDRAM中的这个地址中,将程序的控制权交给uboot,开始执行uboot的代码,xloader的使命也就完成了。在这里,具体的做法就是从header中获取需要加载的SDRAM的地址以及uboot elf目标文件的大小(注意,这里的header是uboot image中的header),然后,使用memcpy将flash中的代码加载到header中指定的SDRAM中的loadaddress。在所有的拷贝工作都完成之后,就直接调用RunUBoot(loadaddress)跳转到这个SDRAM中的地址去执行uboot的代码了。由此,控制权也交到了uboot中。
这里的RunUBoot我们以后再看,但是有一个问题需要在这里说明,那就是关于上面代码中的UBOOT_BASE_ADDRESS。这个宏定义在include/splus_mmap.h中:
7 #define UBOOT_BASE_ADDRESS 0xF8010000 /* flash sector-1 */
从定义这个宏的文件名可以看出,这个基地址的值是与具体的芯片相关的。参看spearplus的手册,可以看到从0xF8000000-0xFBFFFFFF这64M的地址空间是分配给NOR flash的。而在NOR flash中,一个sector是64k,因此,UBOOT_BASE_ADDRESS 0xF8010000也就是flash的sector 1。而flash的sector 0则是给了xloader自己占用了。
这里对flash地址的限制,同时也就限制了xloader和uboot的烧写地址,也就是说,xloader要烧写在地址0xF8000000地址处,而uboot需要烧写在0xF8010000处,这样,才能够正确引导。
上面说了,如果在NOR flash中没有发现uboot image的踪影,xloader将去NAND flash中搜寻是否有可引导的uboot image,如果在那里也找不到,那就只好在原地转圈了。这也就是下面这部份代码所干的事情了。
83 /* UBoot not found in NOR so authenticate it in NAND flash */
84 /* Authenticate Uboot in NAND Flash and copy it in DDR */
85 splus_nand_boot();
86
87 /* wait for infinite if both NAND & NOR boot fails */
88 while(1);
到此为止,如果一切正常的话,我们就应该要离开xloader了。如果我足够心急足够狠心的话,现在就可以抛弃xloader奔向uboot的怀抱了。但是我是一个重感情的人,我不会如此轻易地抛弃一个人:)因此,我还要将xloader中剩余的那部分代码看一看。不过要等到下次了,今天有点事,必须早点下班了,哈。。。