作者:wogoyixikexie@gliet(转载请注明)
前几天这个帖子http://topic.eeworld.net/u/20090605/12/abd72d4e-260c-4e72-966f-298ed343ed3c.html搞的一头雾水,现在看懂了一些,
特写篇博客
我一直使用的是ADS 的bootloader (STEPLDR+eboot整合在一起了),对PB下的bootloader不是很了解,现在有点时间,正好来看看。
像所有bootloader一样,STEPLDR开始就是复位,PB下的bootloader有个特殊情况,就是没有完整的中断向量表,所以是不支持中断的,
要稍作修改才行。我的ADS bootloader具有完整的中断向量以及相关栈的初始化,支持中断。
STARTUPTEXT
LEAF_ENTRY StartUp
b ResetHandler
b .
b .
b .
b .
b .
b .
b .
有此可以看出,STEPLDR只是对复位进行了处理。如果发生别的中断就会B . 死循环了。
这里要提醒的是在eboot中发生的中断时跳到STEPLDR存放中断向量表的地方的,以前我居然认为是跳到eboot中断向量表的地方,真是雷死人了。在ARM结构中,一旦发生中断,PC就会指向0x00000000(bootloader中使用)或者0xffff0000(wince 下使用),所以eboot发生中断,很明显应该跳到STEPLDR的0x00000000,eboot一般已经开了MMU,那么发生中断应该是跳到0x00000000对应的虚拟地址就可以了。
STEPLDR还没有开MMU的,其实STEPLDR就是被自动加载到2440 内部4K SRAM,并完成拷贝eboot到内存并跳转到eboot运行的功能。STEPLDR代码较简单,无非是实现一些复位,休眠唤醒,软复位等功能,在这里就不再赘述了。
现在我就来看PB 的bootloader下的难点——eboot中是如何初始化内存映射表的?
在eboot的startup.s中有
; Define RAM space for the Page Tables:
;
PHYBASE EQU 0x30000000 ; physical start
PTs EQU 0x30010000 ; 1st level page table address (PHYBASE + 0x10000)
; save room for interrupt vectors.前面用来保存中断向量
;-------------------------------------------------------------------------------
下面是内存映射表初始化的地方
下面的注释,很大部分是参考我一个师兄的杰作,在这里贴出他文章地址
http://blog.chinaunix.net/u1/38994/showart_1881778.html
和大家分享一下。
还有我去年的老帖子地址http://topic.eeworld.net/u/20081231/10/BBDE79C2-2884-48E3-9718-90D7FCC1AFA8.html
; Compute physical address of the OEMAddressTable.
20 add r11, pc, #g_oalAddressTable - (. + 8)
ldr r10, =PTs ; (r10) = 1st level page table
; r10=0x30010000
; Setup 1st level page table (using section descriptor)
; Fill in first level page table entries to create "un-mapped" regions
; from the contents of the MemoryMap array.
;
; (r10) = 1st level page table
; (r11) = ptr to MemoryMap array
;0x2000=0x80000000 >> 18 具体原因参考MMU设置 说明这是从cached 地址0x80000000开始的
add r10, r10, #0x2000 ; (r10) = ptr to 1st PTE for "unmapped space"
mov r0, #0x0E ; (r0) = PTE for 0: 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
25 mov r1, r11 ; (r1) = ptr to MemoryMap array
30 ldr r2, [r1], #4 ; (r2) = virtual address to map Bank at
ldr r3, [r1], #4 ; (r3) = physical address to map from
ldr r4, [r1], #4 ; (r4) = num MB to map
cmp r4, #0 ; End of table?
beq %f40
ldr r5, =0x1FF00000
and r2, r2, r5 ; VA needs 512MB, 1MB aligned.
ldr r5, =0xFFF00000
and r3, r3, r5 ; PA needs 4GB, 1MB aligned.
add r2, r10, r2, LSR #18 ;这是MMU设置决定的,再左移回去
add r0, r0, r3 ; (r0) = PTE for next physical page
35 str r0, [r2], #4 ;物理地址存到相应的虚拟地址
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page 1M
sub r4, r4, #1 ; Decrement number of MB left
cmp r4, #0
bne %b35 ; Map next MB
bic r0, r0, #0xF0000000 ; Clear Section Base Address Field
bic r0, r0, #0x0FF00000 ; Clear Section Base Address Field
b %b30 ; Get next element
//创建对应的0xa0000000开始的uncached映射MMU表
//比较C高速缓存是否仍然置位,如果仍然置位了,那么说明还没有执行uncached创建,
//如果C位已经清0,那么说明,uncached循环也执行完毕了,所以跳回到25标号继续创建
40 tst r0, #8
bic r0, r0, #0x0C ; clear cachable & bufferable bits in PTE //清除B写缓冲和C高速缓存
//r10现在对应0x80000000虚拟地址的PTR起始地址,hex(0x20000000>>18)为0x800,
//所以r10 = r10 + 0x800;之后r10指向了0xa0000000虚拟地址对应的PTR起始地址
add r10, r10, #0x0800 ; (r10) = ptr to 1st PTE for "unmapped uncached space"
bne %b25 ; go setup PTEs for uncached space
// 下面这句没有看出有什么作用哦。
sub r10, r10, #0x3000 ; (r10) = restore address of 1st level page table
; Setup mmu to map (VA == 0) to (PA == 0x30000000).
ldr r0, =PTs ; PTE entry for VA = 0 重新加载PTs ,这里没有加上偏移,相当于偏移了0
//所以,这个虚拟地址就是0了,从整体来看,就是物理地址 0x30000000映射到了0x00000000了,汇编真的太难看懂了
ldr r1, =0x3000040E ; uncache/unbuffer/rw, PA base == 0x30000000
str r1, [r0]
; uncached area.加上了0x0800=0x2000000>>18相当于是uncached地址了,不过中断向量的地址是cached的
add r0, r0, #0x0800 ; PTE entry for VA = 0x0200.0000 , uncached
ldr r1, =0x30000402 ; uncache/unbuffer/rw, base == 0x30000000
str r1, [r0]
; Comment:
; The following loop is to direct map RAM VA == PA. i.e.
; VA == 0x30XXXXXX => PA == 0x30XXXXXX for S3C2400
; Fill in 8 entries to have a direct mapping for DRAM
;
ldr r10, =PTs ; restore address of 1st level page table
ldr r0, =PHYBASE // 内存起始物理地址0x30000000
//0x3000 / 4=0x30000000>>18 有时候不知道为什么三星会这么变态,明明是这样了,还不注释好点,或者直接写0x30000000>>18
//让人看了相当郁闷,不知所云。0x30000000映射到0x30000000,似乎没有这个必要。
add r10, r10, #(0x3000 / 4) ; (r10) = ptr to 1st PTE for 0x30000000
add r0, r0, #0x1E ; 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
mov r1, #0
mov r3, #64
45 mov r2, r1 ; (r2) = virtual address to map Bank at
cmp r2, #0x20000000:SHR:BANK_SHIFT //512 M 比较
add r2, r10, r2, LSL #BANK_SHIFT-18
strlo r0, [r2]
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page
subs r3, r3, #1
add r1, r1, #1
bgt %b45
ldr r10, =PTs ; (r10) = restore address of 1st level page table
; The page tables and exception vectors are setup.
; Initialize the MMU and turn it on.
mov r1, #1
mcr p15, 0, r1, c3, c0, 0 ; setup access to domain 0
mcr p15, 0, r10, c2, c0, 0 // C2 -------ldr r10, =PTs
//The CP15:c2 register holds the translation table base address (TTB)—
//an address pointing to the location of the master L1 table in virtual memory(指出L1页表在虚拟内存中的地址).
mcr p15, 0, r0, c8, c7, 0 ; flush I+D TLBs
mov r1, #0x0071 ; Enable: MMU
orr r1, r1, #0x0004 ; Enable the cache
ldr r0, =VirtualStart
cmp r0, #0 ; make sure no stall on "mov pc,r0" below
mcr p15, 0, r1, c1, c0, 0
mov pc, r0 ; & jump to new virtual address
nop
; MMU & caches now enabled.
; (r10) = physcial address of 1st level page table
;
VirtualStart
mov sp, #0x8C000000
add sp, sp, #0x30000 ; arbitrary initial super-page stack pointer
b main 哈哈,跳到main函数了。
现在看来,关于上次eboot中断向量表的讨论,终于有结果了。
因为上面有虚拟地址0 映射到物理地址0x30000000, 虚拟地址0x30000000 映射到物理地址0x30000000
虚拟地址0x8c000000 映射到物理地址0x30000000
在eboot中加入中断的堆栈初始化后,用下面几个地址就可以了。
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r1, r0, #IRQMODE|NOINT
msr cpsr_cxsf, r1 ; IRQMode
ldr sp, = IRQStack
bic r0, r0, #MODEMASK|NOINT
orr r1, r0, #SVCMODE
msr cpsr_cxsf, r1 ; SVCMode.
ldr sp, = SVCStack
#define pISR (*(unsigned *)(0x30000000+0x18))
0x30000000换成过0x00000000,0x8C000000,都可以正常工作
pISR =(unsigned)(0xEA000000)+(((unsigned)IsrHandler - (0x8C000000 + 0x18 + 0x8) )>>2);
上面这句相当于在C语言里面初始化了中断向量表
B IRQHandler
计算原理:B 偏移地址
偏移地址=IsrHandler 所在地址-(boot.bib的起始地址+0x18(这个大家都知道了吧)+0x8(这个是ARM流水线,具体Google了))》2
为什么要友谊两位?这也是B指令的原因。Google一下就明白了。
1、 B 指令
B 指令的格式为:
B{条件} 目标地址
B 指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM
处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,
而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。
它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB 的地址空间)。
现在看来这个和ADS是一致的。
在ADS中,为了支持JLINK等调试
加入了如下映射。
MMU_SetMTT([0x00000000,0x0100000, 0x30000000,RW_CB);
其中0x30000000是ADS的RO base设置地址,是物理地址。
0x00000000是虚拟地址,在JLINK等调试的时候,中断向量表其实已经搬运到内存的0x30000000开始地方。
而2440 此时MMU已经打开,不能识别0x30000000物理地址,一旦中断,就会跳转到0x00000000这个虚拟地址去执行相应的中断handler
(*^__^*) 嘻嘻……,我是这么理解的,不知道对不对,这个问题已经困扰我好久了,以前似懂非懂,现在我觉得这应该是正确的。
在wince下,中断时候就是跳转到0xffff0000地方,我看过下面的代码,确实如此。所以ARM相关文档中0xffff0000以及0x00000000这两个
存放中断向量表的地方应该指的是虚拟地址。
那么,在PB下的2440 eboot+STEPLDR中也应该是一样的。
经过上面的层层映射,0x00000000、0x8C000000以及0x30000000这三个虚拟地址都同时指向物理地址0x30000000,所以这三个虚拟地址
可以互相代替,也是一样的效果,这恰恰证明了MMU的高明——多个虚拟地址可以对应一个物理地址!
不过,在这里我还是有两个疑问。
一、STEPLDR的代码是在2440 的内部4K SRAM 运行的,那中断向量表应该还是在0x0000 0000处才对,难道这时候2440 就把0x0000 0000
看成是虚拟地址了?0x00000000、0x8C000000以及0x30000000这三个虚拟地址都同时指向物理地址0x30000000,但是0x30000000好像和
中断向量表没有什么关系啊,难道这个内部SRAM有自动拷贝到内存初始地址的功能?在三星手册没有啊?哈哈,真是不知道怎么弄的了。
希望高人再帮我解决疑惑了。
二、ADS 的MMU初始化和eboot中的有点小小的差异。
在ADS中
void MMU_SetMTT(int vaddrStart,int vaddrEnd,int paddrStart,int attr)
{
U32 *pTT;
int i,nSec;
pTT=(U32 *)_MMUTT_STARTADDRESS+(vaddrStart>>20);
nSec=(vaddrEnd>>20)-(vaddrStart>>20);
for(i=0;i <=nSec;i++)*pTT++=attr |(((paddrStart>>20)+i) < <20);
}
这里加的偏移地址是(vaddrStart>>20)虚拟地址右移了20位,但是eboot中却是右移了18位,其余的基本一致,请问这是怎么回事。
我看了一下ARM 结构体系相关的英文文档,发现右移20位才合适啊。eboot中咋了,但是确实能正常使用。这点也请高人指示,我想
掌握了ARM中断向量的运用,那我觉得对ARM的认识才算入门了。
哈哈,今天本来公司安排弄摄像头的,可是我一直想着这件事情,就看了一下,结果看了好久才了解一点。总算有点收获了。
当时xy1001跟我在qq里发了堆栈初始化的代码,
我在eboot的fw.s(也就是kernel/hal/arm下的fw.s)里
resethandler函数中加入了
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r1, r0, #IRQMODE|NOINT
msr cpsr_cxsf, r1 ; IRQMode
ldr sp, = IRQStack
bic r0, r0, #MODEMASK|NOINT
orr r1, r0, #SVCMODE
msr cpsr_cxsf, r1 ; SVCMode.
ldr sp, = SVCStack
;;;
代码
#define pISR (*(unsigned *)(0x00000000+0x18))
中断还是没触发,呵呵,找来找去找不到原因,只好放弃了
不知道我的理解对不对:
而2440 此时MMU已经打开,不能识别0x30000000物理地址,一旦中断,就会跳转到0x00000000这个虚拟地址去执行相应的中断handler
(*^__^*) 嘻嘻……,我是这么理解的,不知道对不对,这个问题已经困扰我好久了,以前似懂非懂,现在我觉得这应该是正确的。
意思就是说arm运行的时候走的是虚拟地址,虚拟地址映射到哪里,那么arm实际就到哪里取指。
在wince下,中断时候就是跳转到0xffff0000地方,我看过下面的代码,确实如此。所以ARM相关文档中0xffff0000以及0x00000000这两个
存放中断向量表的地方应该指的是虚拟地址。
arm没加wince之前,来中断的时候跳转到0x00000000+中断地址,这应该是arm硬件默认的。至于加了wince之后,中断跳转到0xffff0000,这应该是软件映射的吧,它是怎么做到arm来中断时,跳转到这里的呢?
那么,在PB下的2440 eboot+STEPLDR中也应该是一样的。
经过上面的层层映射,0x00000000、0x8C000000以及0x30000000这三个虚拟地址都同时指向物理地址0x30000000,所以这三个虚拟地址
可以互相代替,也是一样的效果,这恰恰证明了MMU的高明——多个虚拟地址可以对应一个物理地址!
这里和我想的一样。
不过,在这里我还是有两个疑问。
一、STEPLDR的代码是在2440 的内部4K SRAM 运行的,那中断向量表应该还是在0x0000 0000处才对,难道这时候2440 就把0x0000 0000
看成是虚拟地址了?0x00000000、0x8C000000以及0x30000000这三个虚拟地址都同时指向物理地址0x30000000,但是0x30000000好像和
中断向量表没有什么关系啊,难道这个内部SRAM有自动拷贝到内存初始地址的功能?在三星手册没有啊?哈哈,真是不知道怎么弄的了。
希望高人再帮我解决疑惑了。
这个时候mmu还没有开,物理地址和虚拟地址是一一对应的。用你前面的理论,arm依旧走虚拟地址,只不过STEPLDR这段代码应该把所有中断都关了吧(我没看代码),所以应该不会响应中断。2440我没具体研究过sram,不过2410的sram是映射在0x40000000的位置(没开中断的时候虚实统一),三星的启动代码STEPLDR应该都是小于4K吧,因为如果采用nandfalsh启动,开机以后,arm在硬件上第一件事就是将nandfalsh的前4K程序拷到sram里面,然后执行的。所以前4K代码必须包含拷贝后面程序的操作。开机之后,前4K代码在三星的arm硬件上是自动拷贝到sram的。
二、ADS 的MMU初始化和eboot中的有点小小的差异。
在ADS中
void MMU_SetMTT(int vaddrStart,int vaddrEnd,int paddrStart,int attr)
{
U32 *pTT;
int i,nSec;
pTT=(U32 *)_MMUTT_STARTADDRESS+(vaddrStart>>20);
nSec=(vaddrEnd>>20)-(vaddrStart>>20);
for(i=0;i <=nSec;i++)*pTT++=attr |(((paddrStart>>20)+i) < <20);
}
这里加的偏移地址是(vaddrStart>>20)虚拟地址右移了20位,但是eboot中却是右移了18位,其余的基本一致,请问这是怎么回事。
我看了一下ARM 结构体系相关的英文文档,发现右移20位才合适啊。eboot中咋了,但是确实能正常使用。这点也请高人指示,我想
掌握了ARM中断向量的运用,那我觉得对ARM的认识才算入门了。
不知道我的理解对不对:
你看一下硬件设计,norflash的地址线和sdram的地址线,有点区别。
我的2410的norflash软件设计成了16位(可以是8或16),sdram可以是16位也可以是32位的,eboot我现在为止还没有研究,如果它是运行在32位环境下,那么20和18就可以解释了。是为了地址对齐,才做的20或18的移位。