分类: LINUX
2009-05-10 10:48:42
首先根据链接脚本我们知道程序从u-boot-2009.03/cpu/arm920t/start.S开始,而且入口是_start,因此我们先看start.S, 首先是下面的程序:
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-0x
Address Exception Mode in Entry
0x00000000 Reset Supervisor
0x00000004 Undefined instruction Undefined
0x00000008 Software Interrupt Supervisor
0x
0x00000010 Abort (data) Abort
0x00000014 Reserved Reserved
0x00000018 IRQ IRQ
0x
还有一个问题,了解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,#0x
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位清零指令,0x
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_S
136 /* turn off the watchdog */
137
138 # if defined(CONFIG_S
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 0x
145 # define INTSUBMSK 0x
146 # define CLKDIVN 0x
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_S
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_S
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--- -
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 = 0x
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语言代码就不说了,很容易看懂的。