首先根据链接脚本我们知道程序从u-boot-2009.03/cpu/arm920t/start.S开始,而且入口是_start,因此我们先看start.S,首先是下面的程序:
40 .globl _start
41 _start: b start_code
42 ldr pc, _undefined_instruction
43 ldr pc, _software_interrupt
44 ldr pc, _prefetch_abort
45 ldr pc, _data_abort
46 ldr pc, _not_used
47 ldr pc, _irq
48 ldr pc, _fiq
49
50 _undefined_instruction: .word undefined_instruction
51 _software_interrupt: .word software_interrupt
52 _prefetch_abort: .word prefetch_abort
53 _data_abort: .word data_abort
54 _not_used: .word not_used
55 _irq: .word irq
56 _fiq: .word fiq
42-49行首先42行直接是一个跳转指令,跳到start_code处,43-49行将相应的变量的值装入pc中,其实也是跳转指令,这8行构成了arm架构的异常向量表,共32个字节,是各种异常处理程序的入口,请注意这是硬件决定的,不可更改,当发生了对应的异常的时候,会自动到地址为0x00-0x1c的异常向量表中查找对应的异常处理程序的地址,然后跳转去执行。这里start_code对应的是reset异常,在以前的u-boot版本都是直接用reset的,现在改成了start_code。
Address Exception Mode in Entry
0x00000000 Reset Supervisor
0x00000004 Undefined instruction Undefined
0x00000008 Software Interrupt Supervisor
0x0000000C Abort (prefetch) Abort
0x00000010 Abort (data) Abort
0x00000014 Reserved Reserved
0x00000018 IRQ IRQ
0x0000001C FIQ FIQ
还有一个问题,了解arm指令的朋友可能会对此处使用ldr有疑问,为什么不用adr了,在平常写arm汇编的时候感觉adr比ldr要好用,因为它不用管程序是如何链接的,有时候使用ldr考虑链接的问题会搞得我们焦头烂额的,但是这里可不能这样用,因为adr是不能跨越段的,即不能通过pc计算出其他段的偏移,如果异常处理程序在其他文件或段中就会出错了。
109 start_code:
110 /*
111 * set the cpu to SVC32 mode
112 */
113 mrs r0,cpsr
114 bic r0,r0,#0x1f
115 orr r0,r0,#0xd3
116 msr cpsr,r0
117
118 bl coloured_LED_init
119 bl red_LED_on
113行,将cpsr的内容读到r0中;
114行,bic位清零指令,0x1f=00011111,所以将r0的低5位清成0;
115行,让r0或上0xd3,而0xd3=11010011,即将0,1,4,6,7bit置成了1,模式位对应的0-4bit,10011即为svn模式,之后6,7位置成1表示将禁止irq和fiq。
118行,跳转到函数coloured_LED_init() (在u-boot-2009.03 /lib_arm/board.c中),执行完后返回继续执行red_LED_on(),下面的代码就是这两个函数:
125 void inline __coloured_LED_init (void) {}
126 void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
127 void inline __red_LED_on (void) {}
128 void inline red_LED_on (void) __attribute__((weak, alias("__red_LED_on")));
这里很有必要提一下126行,128行,可以看到在声明这两个函数的时候后面加了__attribute__((weak, alias("xxx"))),这是gcc的扩展属性,这里有两个属性weak, alias,weak属性的作用是将这个函数声明为weak,在这种情况下使用这个函数时,如果有这个函数的定义的话就使用这个函数,如果没有这个函数的定义的话就是一个空函数。而alias的作用是别名,即括号中的xxx跟这个函数是别名,跟linux中的alias的意义是一样的。因此执行118行实际上就是执行__coloured_LED_init(),而119行执行的是__red_LED_on (),它们都是空的,也就是什么都不做。
接下来:
135 #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
136 /* turn off the watchdog */
137
138 # if defined(CONFIG_S3C2400)
139 # define pWTCON 0x15300000
140 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */
141 # define CLKDIVN 0x14800014 /* clock divisor register */
142 #else
143 # define pWTCON 0x53000000
144 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
145 # define INTSUBMSK 0x4A00001C
146 # define CLKDIVN 0x4C000014 /* clock divisor register */
147 # endif
148
149 ldr r0, =pWTCON
150 mov r1, #0x0
151 str r1, [r0]
152
153 /*
154 * mask all IRQs by setting all bits in the INTMR - default
155 */
156 mov r1, #0xffffffff
157 ldr r0, =INTMSK
158 str r1, [r0]
159 # if defined(CONFIG_S3C2410)
160 ldr r1, =0x3ff
161 ldr r0, =INTSUBMSK
162 str r1, [r0]
163 # endif
164
165 /* FCLK:HCLK:PCLK = 1:2:4 */
166 /* default FCLK is 120 MHz ! */
167 ldr r0, =CLKDIVN
168 mov r1, #3
169 str r1, [r0]
170 #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
138-147行定义相关寄存器的地址,pWTCON是看门狗控制寄存器,INTMSK是中断屏蔽寄存器,INTSUBMSK是中断子屏蔽寄存器,CLKDIVN是clock divisor register,用来设置FCLK,HCLK,PCLK三者的比例。
149行,将pWTCON设置为0,即将看门狗关闭。
153-163行,首先156-158行将INTMSK的所有位置为1,即可屏蔽所有中断,159-163行,表示如果是2410的话,会有级联的中断控制器,因此还需要将子中断屏蔽寄存器的所有中断进行屏蔽。
165-170行,将CLKDIVN设置为3,即设置为FCLK:HCLK:PCLK = 1:2:4
176 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
177 bl cpu_init_crit
178 #endif
177行,如果没有定义过CONFIG_SKIP_LOWLEVEL_INIT,就跳到cpu_init_crit处。
236 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
237 cpu_init_crit:
238 /*
239 * flush v4 I/D caches
240 */
241 mov r0, #0
242 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
243 mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
244
245 /*
246 * disable MMU stuff and caches
247 */
248 mrc p15, 0, r0, c1, c0, 0
249 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
250 bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
251 orr r0, r0, #0x00000002 @ set bit 2 (A) Align
252 orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
253 mcr p15, 0, r0, c1, c0, 0
254
255 /*
256 * before relocating, we have to setup RAM timing
257 * because memory timing is board-dependend, you will
258 * find a lowlevel_init.S in your board directory.
259 */
260 mov ip, lr
261 #if defined(CONFIG_AT91RM9200EK)
262
263 #else
264 bl lowlevel_init
265 #endif
266 mov lr, ip
267 mov pc, lr
268 #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
241-243行,失效I/D cache, TLB。
248-253行,关闭mmu和cache,这里249行,将13,9,8bit清零(13—异常向量表基地址:0x0, 9—Disable System Protection, 8—Disable ROM Protection),250行,将7,2,1,0bit清零(7—为0的时候表示小端字节序,2-- Data Cache Disabled,1-- Alignment Fault checking disabled,0—为0的话MMU disabled),251行,将bit 1 设置为1表示Fault checking enabled,252行,将bit 12设置为1表示使能 I-Cache。
上面的几条指令都是协处理器指令,比较固定,看2410的datasheet就可以找到。
260行,因为下面要继续调用子程序,所以需要将之前已经保存在lr中的pc值再保存在ip中,之后261行,将pc值保存到lr中,跳转到lowlevel_init,这跟函数在u-boot-2009.03/board/samsung/smdk2410/lowlevel_init.S下:
129 _TEXT_BASE:
130 .word TEXT_BASE
131
132 .globl lowlevel_init
133 lowlevel_init:
134 /* memory control configuration */
135 /* make r0 relative the current location so that it */
136 /* reads SMRDATA out of FLASH rather than memory ! */
137 ldr r0, =SMRDATA
138 ldr r1, _TEXT_BASE
139 sub r0, r0, r1
140 ldr r1, =BWSCON /* Bus Width Status Controller */
141 add r2, r0, #13*4
142 0:
143 ldr r3, [r0], #4
144 str r3, [r1], #4
145 cmp r2, r0
146 bne 0b
147
148 /* everything is fine now */
149 mov pc, lr
150
151 .ltorg
152 /* the literal pools origin */
153
154 SMRDATA:
155 .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
156 .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
157 .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
158 .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
159 .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
160 .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
161 .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
162 .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
163 .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
164 .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
165 .word 0x32
166 .word 0x30
167 .word 0x30
整个lowlevel_init.S文件中就只有这个函数,相关的宏定义非常的长,就不全部贴出来了,这里说说这个函数到底做了什么。因为后面我们不管是将内核copy到内存中,还是在u-boot中执行程序,都涉及到内存,flash,外设等的使用,因此在使用前必须要保证他们可用,而存储控制器就是用来控制它们的,所以我们这里就需要先对其进行初始化。这里需要先简单讨论一下存储控制器,2410平台上存储控制器共有13个寄存器,BANK0-BANK5用来接外设,flash,因此只需要设置BWSCON和BANKCONx(x为0-5),而BANK6,BANK7接SDRAM,除了设置BWSCON和BANKCONx(x为6,7),还需要设置其他四个寄存器,而这13个寄存器的地址是连续的,BWSCON是第一个寄存器,详细情况件2410的datasheet。
137-138行,SMRDATA为154-167行这13个寄存器(后面讨论)的值的开始地址,137行将其保存在r0中,_TEXT_BASE的值为TEXT_BASE,而TEXT_BASE = 0x33F80000在u-boot-2009.03/board/samsung/smdk2410/config.mk中进行定义,138行r1为0x33F80000。
139行,计算出SMRDATA跟TEXT_BASE之间的偏移保存到r0中,也就是在SRAM中SMRDATA数据的首地址,因为u-boot.bin之前是经过链接的,因此链接时在程序中SMRDATA数据的首地址是相对TEXT_BASE的,而现在数据在SRAM中的时候,SMRDATA数据的首地址是相对0的,因此当前SRAM中的SMRDATA数据的首地址 = SMRDATA跟TEXT_BASE之间的偏移-0 = SMRDATA跟TEXT_BASE之间的偏移。
140行将 BWSCON的寄存器的地址保存在r1中,141行r2为r0+13*4,其实就是最后一个寄存器的值的地址,即167行对应的数据的地址,它是用来控制循环的。
142-146行,循环的将SMRDATA的13个数据写入存储控制器的13个寄存器中。
149行,返回调用的函数 cpu_init_crit,这样我们又回到了start.S中,继续执行到start.S的266-267行,在返回调用 cpu_init_crit的地方继续执行180行。
180 #ifndef CONFIG_SKIP_RELOCATE_UBOOT
181 relocate: /* relocate U-Boot to RAM */
182 adr r0, _start /* r0 <- current position of code */
183 ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
184 cmp r0, r1 /* don't reloc during debug */
185 beq stack_setup
186
187 ldr r2, _armboot_start
188 ldr r3, _bss_start
189 sub r2, r3, r2 /* r2 <- size of armboot */
190 add r2, r0, r2 /* r2 <- source end address */
191
192 copy_loop:
193 ldmia r0!, {r3-r10} /* copy from source address [r0] */
194 stmia r1!, {r3-r10} /* copy to target address [r1] */
195 cmp r0, r2 /* until source end addreee [r2] */
196 ble copy_loop
197 #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
这段代码的作用是为了bootloader的第二阶段(即monitor)作准备的,将完整的u-boot代码copy到内存中,后面就跳到内存的代码中去执行,你如果想在u-boot中执行代码,进行调试的话,就可以用到了它,但是一般在最终的产品发布的时候一般并不需要这样的。
182-185行,r0是当前代码的起始地址,而r1是_TEXT_BASE,即正常情况下u-boot在内存中的起始地址,184行,比较了r0,r1就可以判断当前是否在内存中,如果相等表示已经在内存中了,就不用复制了直接跳转到stack_setup,如果不再内存中就还需要将u-boot复制到内存的_TEXT_BASE的地方。
187-197行,看过上面lowerlevel_init中copy数据的方法,这里就很好明白了,187行,将_armboot_start对应的地址传到r2里,188行将_bss_start对应的对应的地址传给r3, _armboot_start在start.S的前面定义,如下:
77 .globl _armboot_start
78 _armboot_start:
79 .word _start
可以看到_armboot_start
就是_start,也就是程序的开始地址,而_bss_start定义在
84 .globl _bss_start
85 _bss_start:
86 .word __bss_start
而__bss_start定义在链接脚本u-boot-2009.03/board/samsung/smdk2410/u-boot.lds中
28 SECTIONS
29 {
30 . = 0x00000000;
31
32 . = ALIGN(4);
33 .text :
34 {
35 cpu/arm920t/start.o (.text)
36 *(.text)
37 }
38
39 . = ALIGN(4);
40 .rodata : { *(.rodata) }
41
42 . = ALIGN(4);
43 .data : { *(.data) }
44
45 . = ALIGN(4);
46 .got : { *(.got) }
47
48 . = .;
49 __u_boot_cmd_start = .;
50 .u_boot_cmd : { *(.u_boot_cmd) }
51 __u_boot_cmd_end = .;
52
53 . = ALIGN(4);
54 __bss_start = .;
55 .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
56 _end = .;
57 }
之后189行,r2=r3-r2=__bss_start - _start=代码段的长度
190行,r2=r0+r2=_start+代码段的长度=当前代码段的结束地址
192-196行, copy u-boot的代码到内存中来
上面程序在不管是否需要将u-boot copy到内存中去,都会执行到下面的 stack_setup来:
199 /* Set up the stack */
200 stack_setup:
201 ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
202 sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
203 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
204 #ifdef CONFIG_USE_IRQ
205 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
206 #endif
207 sub sp, r0, #12 /* leave 3 words for abort-stack */
208
209 clear_bss:
210 ldr r0, _bss_start /* find start of bss segment */
211 ldr r1, _bss_end /* stop here */
212 mov r2, #0x00000000 /* clear */
213
214 clbss_l:str r2, [r0] /* clear loop... */
215 add r0, r0, #4
216 cmp r0, r1
217 ble clbss_l
218
219 ldr pc, _start_armboot
220
221 _start_armboot: .word start_armboot
这段代码的作用是建立堆栈,和一些必要的段,同上面,这里也是为了后面服务的,如果你不打算进入下载模式的话,不想在u-boot中执行程序,那么这段代码有没有是无所谓的,因为,内核启动之后是会重新对这些内存地址进行设置的。
201行,r0=_TEXT_BASE, 202行,r0=r0- CONFIG_SYS_MALLOC_LEN,这样之后,这是在为malloc预留空间,也就是说_TEXT_BASE下CONFIG_SYS_MALLOC_LEN长的范围是malloc使用的区域。
203行,为全局参数预留内存空间
204-206行,如果打算使用IRQ,FIQ模式,就为他们预留内存空间
207行,为abort异常预留12字节的空间,并将当前的地址赋给sp,这样就为内存栈设置好了,之后如果在u-boot中运行程序时需要使用栈的时候就从这里开始。
209-217行,清除bss段。
219行,跳转到_start_armboot,也就是函数 start_armboot,此函数存放在u-boot-2009.03/lib_arm/board.c,这样就到了u-boot的第二阶段了。
进入了第二阶段,程序其实也就是进入了下载模式,这个阶段只是为了在u-boot环境下运行程序而存在的,即假如你只想在在初始化之后就启动内核的话,这个阶段没有也是没有关系的,这样的话,你就需要更改代码,将复制u-boot完整代码之后的代码全部不要,并加上将内核copy至指定地址,并建好tag list段就好了,设置好r0,r1,r2的值,跳转到内核的起始地址就可以将内核启起来了。这里第二阶段的c语言代码就不说了,很容易看懂的。