Sunplus DVD软件Makefile详解(五)
xluoly@hotmail.com<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
链接脚本文件
为了理解romL.bin和rom.bin的生成原理,我们还需要理解链接脚本文件dvdL.ld。链接脚本是协助链接工具完成任务的,它主要描述的内容包括:输入段与输出段之间的关系、各个输出段在系统存储器中的布局等信息。最终生成可固化到ROM(flash)的可执行程序。一般地,可ROM化的程序具有下结构。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
下面我们来看看SPHE8104L的可执行程序是怎样存放的。前面说过,dvdL.ld是dvdL.ldp经cpp处理后生成的文件,因此我们直接看dvdL.ld文件会更清楚些。下面是dvdL.ld的内容:
dvdL.ld
1 INPUT(-lsys)
2 INPUT(-latapi)
3 INPUT(-lsys)
4 INPUT(-lir)
5 INPUT(-lpe)
6 INPUT(-ltask)
7 INPUT(-lauddrv)
8 INPUT(-ldsp3)
9 INPUT(-lauth)
10 INPUT(-lparser)
11 INPUT(-lfs_krnl)
12 INPUT(srv-elf\libsrv_sa7km0_slt_8104T_.a)
13 INPUT(-lsrvdp_8104T_)
14 GROUP( -lkern_8202L)
15 INPUT(-lstd)
16 INPUT(-lm)
17 INPUT(-lgcc)
18 INPUT(-lsys)
19 INPUT(-latapi)
20 INPUT(-lsys)
21 INPUT(-lir)
22 INPUT(-lpe)
23 INPUT(-ltask)
24 INPUT(-lauddrv)
25 INPUT(-ldsp3)
26 INPUT(-lauth)
27 INPUT(-lparser)
28 INPUT(-lfs_krnl)
29 INPUT(srv-elf\libsrv_sa7km0_slt_8104T_.a)
30 INPUT(-lsrvdp_8104T_)
31 GROUP( -lkern_8202L)
32 INPUT(-lstd)
33 INPUT(-lm)
34 INPUT(-lgcc)
35
36 SECTIONS
37 {
38 .drv_iop :
39 {
40 _text_drv_iop = .;
41 . = ALIGN(16);
42 KEEP(*iop.o(.rodata*))
43 } > sdram_drv_iop
44
45 .drv_iop_rst :
46 {
47 . = ALIGN(16);
48 KEEP(*ioprom.o(.rodata*))
49 } > sdram_iop_rst
50 }
51
52 SECTIONS
53 {
54 .rom1 :
55 {
56 MIPS/init0.o (.text*)
57 MIPS/rominit.o (.text*)
58 MIPS/crt0.o (.text*)
59 *reset.o (.text*)
60 *sysmain.o (.text*)
61 *lexra.o (.text*)
62 *bitop.o (.text*)
63 *intdrv.o (.text*)
64 *other.o (.text*)
65 *hwcpu0.o (.text*)
66 *hwcpu1.o (.text*)
67 *crt0.o (.rodata*)
68 *init0.o (.rodata*)
69 *watchdog.o* (.text*)
70 . = ALIGN(16);
71 } > rom
72
73 .ram1 :
74 {
75 _data = .;
76 * (.data*)
77 _gp = .;
78 * (.sdata*)
79 * (.lit8*)
80 * (.lit4*)
81 * (.lita*)
82 _edata = .;
83 . = ALIGN(16);
84 } > sdram_cs
85
86 .rom1_2 :
87 {
88 _data_ps = .;
89 . += SIZEOF(.ram1);
90 _data_pe = .;
91 . = ALIGN(16);
92 } > rom
93
94 .rom2 :
95 {
96 MIPS/rominit.o (.rodata*)
97 MIPS/crt0.o (.rodata*)
98 *reset.o (.rodata*)
99 *sysmain.o (.rodata*)
100 *lexra.o (.rodata*)
101 *bitop.o (.rodata*)
102 . = ALIGN(16);
103 } > rom
104
105 .drv_ap1 :
106 {
107 _text_drv_ap = .;
108 * (.rdata*)
109 * (.rodata*)
110 * (.text*)
111 . = ALIGN(16);
112 } > sdram_ap1
113
114 .ramF :
115 {
116 MIPS/databuf.o (.bss*)
117 MIPS/databuf.o (COMMON*)
118 . = ALIGN(16);
119 } > sdram_cs
120
121 .ram2 :
122 {
123 _bstart = .;
124 * (.sbss*)
125 * (.scommon*)
126 * (.bss*)
127 * (COMMON*)
128 . = ALIGN(16);
129 _bend = .;
130 } > sdram_cs
131
132 .rel :
133 {
134 * (.rel.dyn)
135 } > garbage
136
137 _etext = ADDR(.rom1) + SIZEOF(.rom1) + SIZEOF(.rom2);
138 _stextf = _etext + SIZEOF(.ram1);
139 _tt_gb_size = SIZEOF(.ram1) + SIZEOF(.ram2) + SIZEOF(.ramF);
140
141 .udf_buf :
142 {
143 _udf_work_buf = .;
144 . += (1024*((40)));
145 _udf_work_buf_end = .;
146 } > sdram_udfbuf
147
148 wb_buf = (0x080000000+1024*(((((((((0) + 0) + (2))+(3))) + (4)) + (18)))));
149 ADPCM_ESP_var = (0x0bfff0000+1024*(0)+0);
150 _stkptr = (0x080000000+1024*(((((0) + 0) + (2))+(3))));
151 _stkptr_dbg = (0x080000000+1024*((((2048) - (300+50)))));
152 _stkbtm = (0x080000000+1024*(((((0) + 0) + (2))+(3))-((3)+1)));
153 _stkbtm_dbg = (0x080000000+1024*((((2048) - (300+50)))-(2)));
154 }
155
156 NOCROSSREFS( .ramdrv1 .ramdrv2 )
157 NOCROSSREFS( .drv_ap2 .drv_ap3 )
158
159 MEMORY
160 {
161 garbage : org = 0x00000000, l = 1m
162 bootrom : org = 0xbfc00000, l = 512k
163 rom : org = 0x88000000, l = 1024k
164 sdram_cs : org = (0x080000000+1024*(((((((0+0)+(2))+(3))) + (4)))), \
165 l = (1024*((18)))
166 sdram_drv_iop : org = 0x080000000, l = 2k
167 sdram_iop_rst : org = 0x180000000, l = 2k
168 sdram_ap1 : org = (0x080000000+1024*(((2048) - (300+50)))), \
169 l = (1024*((300+50)))
170 sdram_udfbuf : org = (0x080000000+1024*((((((((((0) + 0) \
171 + (2))+(3))) + (4)) + (18)))+(32)))), \
172 l = (1024*((40)))
173 }
174
175 ENTRY(__romstart)
178
1~34)设置链接输入文件,这里列出的都是静态链接库文件。前面已经介绍过,每个子模块的编译结果都是生成一个库文件,因此这里把它们当成输入文件链接进来。“INPUT(FILE1 FILE2 ...)”:输入命令促使链接器在链接时包含列出的文件,就像在命令行指定文件名一样。“GROUP(FILE FILE ...)”:这个命令类似“INPUT”,但是所有命名文件完全是归档文件,它们被重复搜索直到不再有未定义的参考(references)。
175)定义程序入口为“__romstart”。这个symbol位于Source Code文件init0_8202.S中。
159~173)定义各个程序段(program segment)的起始地址和长度。注意一下sdram_iop_rst,它的起始地址是0x180000000,实际上已经超出了MIPS内核的寻址空间,最终会被强制变为0x80000000。不知道这个模块有没有真正起作用,我没有测试过。起始地址为0x80000000的空间是SDRAM。起始地址为0x88000000的空间是ROM(系统中用的是flash)。
36~50)定义iop相关的两个section .drv_iop和.drv_iop_rst。分别存放到segment sdram_drv_iop和sdram_iop_rst中。
54~71)定义section .rom1。其中存放的内容是系统启动阶段的程序代码(.text)和只读数据(.rodata),存放到segment rom中。它是放入rom的第一段内容,因此它的其起始地址就是rom的起始地址0x88000000。
73~84)定义section .ram1。其中存放所有输入文件中的某些数据段(.data,.sdata,.lit8,.lit4,.lita),这些段存放可写的、为初始化的变量。它们被定位到segment sdram_cs中,起始地址等于sdram_cs的起始地址。同时用_data和_edata两个symbol分别记录.ram1的起始和结束地址,后面会介绍这两个symbol的作用。_gp的值用于初始化MIPS内核全局变量指针(寄存器$28),相应的程序在源代码文件crt0_8202.S。
86~92)定义section .rom1_2。内容为空,预留一个大小等于.ram1长度的空间,定位在程序段rom内,起始地址紧接在.rom1之后。用_data_ps和_data_pe记录.rom1_2起始和结束的地址。从前面介绍过的romL.dump生成romL.bin的过程可以看出,实际上存放在.rom1_2中的是.ram1的映像。因为.ram1中存放有全局变量的初始化值,所以必须把它们固化到rom空间,在系统启动阶段将它们copy到SDRAM中_data值对应的地址。这个过程就是文件rominit.c中函数rominit()的工作,在crt0_8202.S中被调用。
94~103)定义section .rom2。存放的内容是启动程序的部分只读数据(其实应该可以并入.rom1),定位在rom中,起始地址紧接在.rom1_2之后。
105~112)定义section .drv_ap1。存放的内容是未指明文件的只读数据段(.rdata,.rodata)和程序段(.text),定位在sdram_cs中,起始地址紧接在.ram1之后,用_text_drv_ap保存起始地址,在程序中会用到。从前面介绍的Makefile中可以看到,这个section被压缩程序处理后,保存在romL.bin文件中,也就是保存在flash中。程序运行时,从flash中对应地址读出并解压缩后存入SDRAM中起始地址等于_text_drv_ap值的位置,然后程序指针跳转到这部分code的入口地址运行,这个过程见源程序函数LoadModual()。这是为了优化程序的运行速度而设计的一种运行模式,因为内核对SDRAM的读取速度远远快于对flash。
114~130)定义section .ramF和.ram2。存放的内容是输入文件中的未初始化变量,定位在sdram_cs中,起始地址紧接在.drv_ap1之后。
132~135)定义section .rel,定位在起始地址为0x00000000的1MB空间,实际上这是一段无效的空间,并不用于生成最后的可执行程序,只是用于收集链接过程中的垃圾信息。
141~146)定义section .udf_buf。定位在sdram_udfbuf,只是预留一段SDRAM的空间在程序中用到。
148~153)计算这几个symbol的值,供程序中使用。其中包括主任务(main task)和调试任务(debug task)的堆栈位置(用于实际生产的程序是只运行主任务的,开发中用于调试的程序会增加一个调试任务,两个任务采用时间片轮转调度方法)。
156~157)检查列出的section,如果发现它们之间有相互引用就会报错。这里列出的四个section实际上不存在,不起作用(应该是DVD软件中遗留下来的,多余)。
162)这里定义了一个名为bootrom的segment,起始地址为0xbfc00000,长度为512KB,这里没有用到。MIPS内核的上电复位向量地址是0xbfc00000,据说IC内部已经固化有一段上电启动程序。应用程序存放在外部flash中,接口是SPI,与NOR flash不同,它是不能直接被CPU内核访问到的。因此,可以猜测IC内部有一部分启动代码,在CPU执行到外部SPI flash中的程序之前,由它完成CPU的线性寻址方式到外部SPI通信的转换。然后才将程序指针(PC)跳转到0x88000000,这就是我们看到的程序入口地址(__romstart)。
下面是用readelf –S看到的输出段情况,有些段是ld默认生成的。
下面是最终生成的rom.bin文件,也是flash中的数据组成。
以上介绍的SPHE8104L是Sunplus的一款CD/USB/SD解决方案SOC。从DVD IC衍生而来,硬件上比DVD IC少了视频等模块,基本软件也是由DVD软件裁剪而成,只是软件架构是重新设计了,比DVD软件简洁多了,看着都不像Sunplus糟糕的代码风格了。但是,里面还是保留了不少DVD软件的痕迹,因此,如果不清楚这点历史的话,可能看着会有点疑惑。
这份软件最终生成的可执行程序size没有DVD的那么庞大,因此,除了启动阶段的代码是直接从flash运行外,其余部分都是先将代码压缩后保存在flash中,由启动代码将其读入SDRAM内解压缩运行。DVD软件也是采用这样的思想,它将整个系统按功能模块分割成不同的section,再将这些section转换成独立的可执行的代码,按一定顺序保存的flash中,需要运行到哪个模块就将其读入SDRAM运行。这样的设计还有另外一个好处,就是可以减小保存在flash中的code size。代价是需要占用一段SDRAM空间保存这部分可执行代码。也并不是每个模块都需要独立占用一段SDRAM空间的,如果是完全独立(没有相互引用)的两个模块,就可以在SDRAM中给它们安排同一段地址,运行之前将其读入SDRAM中,相同地址范围的代码可以相互覆盖。例如,CD和USB的播放是两个独立的模块,这样就可以将它们放到同一段SDRAM空间运行。载入SDRAM中运行的程序段,其实也不一定要保存在flash中,保存在U盘或者SD卡,甚至通过UART下载理论上都是可以的,只要编写好相应的装载程序就行了。
至此,相关内容我们都已经介绍了。虽然Makefile中还有相当部分设计不够合理,也有很过重复和多余的内容,显得有些凌乱,而且整个构建过程过多的依赖dos批处理脚本,没有充分发挥出Makefile的管理功效,但是能做到这样已经是非常难能可贵了。相信能够看到这里的同志,一定是不缺乏耐心的,这些问题是不会成为障碍的。由于缺乏IC硬件和软件的相关资料,上文中某些理解只能是本人的推测,留待考证……
参考资料:
l Sunplus SPHE8104L source code
l 《GCC - The Complete Reference McGraw Hill》
l 《GNU Make》
l 《Using Ld》
l 《See MIPS Run-2nd_edition》
『完』
文章评论(0条评论)
登录后参与讨论