分类: LINUX
2010-10-13 09:55:38
\kernel\arch\arm\boot\compressed\ head.S分析(2)
__armv7_mmu_cache_on:
mov
r12, lr //注意,这里需要手工保存返回地址!!这样做的原因是下面的bl指令会覆盖掉原来的lr,为保证程序正确返回,需要保存原来lr的值
bl __setup_mmu
mov
r0, #0
mcr
p15, 0, r0, c7, c10, 4 @ drain write buffer
mcr
p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
mrc
p15, 0, r0, c1, c0, 0 @ read control reg
orr r0,
r0,
#0x5000 @
I-cache enable, RR cache replacement
orr r0,
r0, #0x0030
bl __common_mmu_cache_on
mov
r0, #0
mcr
p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
mov
pc, r12 //返回到cache_on
这个函数首先执行__setup_mmu,然后清空write buffer、I/Dcache、TLB.接着打开i-cache,设置为Round-robin replacement。调用__common_mmu_cache_on,打开mmu和d-cache.把页表基地址和域访问控制写入协处理器寄存器c2、c3. __common_mmu_cache_on函数数定义如下:
__common_mmu_cache_on:
#ifndef DEBUG
orr r0,
r0,
#0x000d @
Write buffer, mmu
#endif
mov
r1, #-1 //-1的补码是ffff ffff,
mcr
p15, 0, r3, c2, c0, 0 @ 把页表地址存于协处理器寄存器中
mcr
p15, 0, r1, c3, c0, 0 @设置domain
access control寄存 器
b
.align 5 @
cache line aligned
1: mcr
p15, 0, r0, c1, c0, 0 @ load control
register
mrc
p15, 0, r0, c1, c0, 0 @ and read it back to
sub pc,
lr, r0, lsr #32 @ properly flush pipeline
重点来看一下__setup_mmu这个函数,定义如下:
__setup_mmu: sub r3,
r4, #16384 @ Page directory size
bic r3,
r3, #0xff @ Align
the pointer
bic r3,
r3, #0x
这里r4中存放着内核执行地址,将16K的一级页表放在这个内核执行地址下面的16K空间里,上面通过 sub r3, r4,
#16384 获得16K空间后,又将页表的起始地址进行16K对齐放在r3中。即ttb的低14位清零。
//初始化页表,并在RAM空间里打开cacheable 和bufferable位
mov
r0, r3
mov
r9, r0, lsr #18
mov
r9, r9, lsl #18 @ start of
RAM
add r10,
r9, #0x10000000 @ a reasonable RAM size
上面这几行把一级页表的起始地址保存在r0中,并通过r0获得一个ram起始地址(每个页面大小为
mov
r1, #0x12 //一级描述符的bit[1:0]为10,表示这是一个section描述符。也即分页方式为段式分页
orr r1,
r1, #3 << 10 //一级描述符的access
permission bits bit[11:10]为11. 即
add r2,
r3, #16384 //一级描述符表的结束地址存放在r2中。
1: cmp
r1,
r9 @
if virt > start of RAM
orrhs r1,
r1, #0x
cmp
r1,
r10 @
if virt > end of RAM
bichs r1,
r1, #0x
str r1,
[r0],
#4 @ 1:1
mapping
add r1,
r1, #1048576//下个
teq r0,
r2
bne 1b
因为打开cache前必须打开mmu,所以这里先对页表进行初始化,然后打开mmu和cache。
上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的地址是否在那个
页表大小为16K,每个描述符4字节,刚好可以容纳4096个描述符,每个描述符映射
mov
r1, #0x1e
orr r1,
r1, #3 << 10 //这两行将描述的bit[11:10]
bit[4:1]置位,
//具体置位的原因,在ARM11的页表项描述符里有说明,由于没找到完整的文档,这里只给出图示:
mov
r2, pc, lsr #20
orr r1,
r1, r2, lsl #20 //将当前地址进
add r0,
r3, r2, lsl #2 //r3为刚才建立的一级描述符表的起始地址。通过将当前地
//址(pc)的高12位左移两位(形成14位索引)与r3中的地址
//
(低14位为0)相加形成一个4字节对齐的地址,这个
//地址也在16K的一级描述符表内。当前地址对应的
//描述符在一级页表中的位置
str r1,
[r0], #4
add r1,
r1, #1048576
str r1,
[r0] //这里将上面形成的描述符及其连续的下一个section描述
//写入上面4字节对齐地址处(一级页表中索引为r2左移
//2位)
mov
pc, lr //返回,调用此函数时,调用指令的下一语句mov r0,
#0的地 址保存在lr中
这里进行的是一致性的映射,物理地址和虚拟地址是一样。
__common_mmu_cache_on最后执行mov pc, r12返回cache_on,为何返回到的是cache_on呢?这就是上面解释保存lr的原因,因为原来的lr保存了 执行
bl cache_on语句的下条指令,因此能正确返回!
下一条指令也即是下面开始
mov
r1,
sp @栈空间大小是4096字节,那//么在栈空间地址上面再分配64K字节空间
add r2,
sp, #0x10000 @ 分配64k字节。
栈的分配如下:
.align
.section
".stack", "w"
user_stack: .space 4096//lc0对SP进行了定义 .word user_stack+4096 @
sp
由此可见sp是往下增长的
分配了解压缩用的缓冲区,那么接下来就判断这个数据区是否和我们目前运行的代码空间重叠,如果重叠则需调整
/*
* Check to see if we will overwrite
ourselves.
* r4 = final kernel
address
* r5 = start of this
image
* r2 = end of malloc
space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length
<= r5 -> OK
*/
cmp
r4, r2
bhs wont_overwrite
sub r3,
sp, r5 @ > compressed kernel
size
add r0,
r4, r3, lsl #2 @ allow for 4x expansion
cmp
r0, r5
bls wont_overwrite
缓冲区空间的起始地址和结束地址分别存放在r1、r2中。然后判断最终内核地址,也就是解压后内核的起始地址,是否大于malloc空间的结束地址,如果大于就跳到wont_overwrite执行,wont_overwrite函数后面会讲到。否则,检查最终内核地址加解压后内核大小,也就是解压后内核的结束地址,是否小于现在未解压内核映像的起始地址。小于也会跳到wont_owerwrite执行。如两这两个条件都不满足,则继续往下执行。
mov
r5,
r2 @
decompress after malloc space
mov
r0, r5
mov
r3, r7
bl decompress_kernel
这里将解压后内核的起始地址设为malloc空间的结束地址。然后后把处理器id(开始时保存在r7中)保存到r3中,调用decompress_kernel开始解压内核。这个函数的四个参数分别存放在r0-r3中,它在arch/arm/boot/compressed/misc.c中定义。 解压的过程为先把解压代码放到缓冲区,然后从缓冲区在拷贝到最终执行空间。
add r0,
r0, #127
bic r0,
r0, #127 @
align the kernel length
/*
* r0 =
decompressed kernel length
* r1-r3 = unused
* r4 =
kernel execution address
* r5 =
decompressed kernel start
* r6 =
processor ID
* r7 =
architecture ID
* r8 =
atags pointer
* r9-r14 = corrupted
*/
add r1,
r5, r0 @ end of decompressed
kernel
adr r2,
reloc_start
ldr r3,
LC1
add r3,
r2, r3
1: ldmia r2!,
{r9 -
r14} @
copy relocation code
stmia r1!,
{r9 - r14}
ldmia r2!,
{r9 - r14}
stmia r1!,
{r9 - r14}
cmp
r2, r3
blo 1b
这里首先计算出重定位段,也即reloc_start段,然后对它的进行重定位
bl cache_clean_flush
add pc,
r5, r0 @ call relocation code
重定位结束后跳到解压后执行 b call_kernel,不再返回。call_kernel定义如下:
call_kernel:
bl cache_clean_flush
bl cache_off
mov r0,
#0 @
must be zero
mov r1,
r7 @
restore architecture number
mov r2,
r8 @
restore atags pointer
mov pc,
r4 @
call kernel
在运行解压后内核之前,先调用了
cache_clean_flush这个函数。这个函数的定义如下:
cache_clean_flush:
mov
r3, #16
b call_cache_fn
其实这里又调用了call_cache_fn这个函数,注意,这里r3的值为16,上面对cache操作已经比较详细,不再讨论。
刷新cache后,则执行mov pc, r4跳入内核,开始进行下个阶段的处理。
整个代码流程如下:
chinaunix网友2010-10-13 20:26:53
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com