arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵

来源:互联网 发布:苹果手机电池查询软件 编辑:程序博客网 时间:2024/05/22 04:48

                                            arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵

转载地址:http://blog.csdn.net/skyflying2012/article/details/41344377

前段时间移植uboot仔细研究过uboot启动过程,最近耐不住寂寞,想对kernel下手。

Uboot启动过程分析博文连接如下:


http://blog.csdn.net/skyflying2012/article/details/25804209


移植内核时kernel启动过程需要我们修改的地方比较少,研究这个对于编写driver也没有多大帮助,但对了解整个Linux架构,各种机制还是非常有用。


只有知道kernel如何启动,我们才能真正的去理解kernel

作为一个嵌入式工作者,我想不能仅仅局限于某个module driver,而应深入到kernel的汪洋大海中去傲游!


学习启动过程,我本着打破沙锅问到底的原则,希望能研究的明明白白,但也鉴于水平有限,还是有很多纰漏之处

共享博文,希望大家多多交流指正,辛苦整理,如需转载,还请注明出处。

对于arm linux,start_kernel之前都是汇编代码,区区上百行汇编,但是却蕴含着很多精髓。

这部分代码分3篇来分析,另外两篇链接地址如下:

http://blog.csdn.net/skyflying2012/article/details/41447843

http://blog.csdn.net/skyflying2012/article/details/48054417


今天先来学习前几十行!


Kernel版本号:3.4.55

在arch/arm/kernel/head.S中,如下:

[plain] view plain copy
  1. .arm  
  2.   
  3.     __HEAD  
  4. ENTRY(stext)  
  5.   
  6.  THUMB( adr r9, BSYM(1f)    )   @ Kernel is always entered in ARM.  
  7.  THUMB( bx  r9      )   @ If this is a Thumb-2 kernel,  
  8.  THUMB( .thumb          )   @ switch to Thumb now.  
  9.  THUMB(1:           )  
  10.   
  11.     //处理器进入svc模式,关闭中断  
  12.     setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode  
  13.                         @ and irqs disabled  
  14.     //获取处理器ID  
  15.     mrc p15, 0, r9, c0, c0      @ get processor id  
  16.     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid  
  17.     //将proc_type_list pointer存在r10中,如果为NULL,则error_p  
  18.     movs    r10, r5             @ invalid processor (r5=0)?  
  19.  THUMB( it  eq )        @ force fixup-able long branch encoding  
  20.     beq __error_p           @ yes, error 'p'  
  21.   
  22.     //CONFIG_ARM_LPAE不太明白含义,我使用处理器配置文件没有选择该项,感兴趣朋友可以研究下  
  23. #ifdef CONFIG_ARM_LPAE  
  24.     mrc p15, 0, r3, c0, c1, 4       @ read ID_MMFR0  
  25.     and r3, r3, #0xf            @ extract VMSA support  
  26.     cmp r3, #5              @ long-descriptor translation table format?  
  27.  THUMB( it  lo )                @ force fixup-able long branch encoding  
  28.     blo __error_p           @ only classic page table format  
  29. #endif  
  30. #ifndef CONFIG_XIP_KERNEL  
  31.     //获取物理地址与虚拟地址的offset,存在r8中  
  32.     adr r3, 2f  
  33.     ldmia   r3, {r4, r8}  
  34.     sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)  
  35.     add r8, r8, r4          @ PHYS_OFFSET  
  36. #else  
  37.     //定义CONFIG_XIP_KERNEL,offset为PHYS_OFFSET  
  38.     ldr r8, =PHYS_OFFSET        @ always constant in this case  
  39. #endif  
  40.   
  41.     /*  
  42.      * r1 = machine no, r2 = atags or dtb,  
  43.      * r8 = phys_offset, r9 = cpuid, r10 = procinfo  
  44.      */  
  45.     //对bootloader传来的tags参数进行检查  
  46.     bl  __vet_atags  
Kernel的入口函数是哪个,入口地址在哪,需要根据连接脚本来确定。
在arch/arm/kernel/vmlinux.lds.S,如下:

[plain] view plain copy
  1. OUTPUT_ARCH(arm)  
  2. ENTRY(stext)  
  3.   
  4. #ifndef __ARMEB__  
  5. jiffies = jiffies_64;  
  6. #else  
  7. jiffies = jiffies_64 + 4;  
  8. #endif  
  9.   
  10. SECTIONS  
  11. {  
  12. ........  
  13. #ifdef CONFIG_XIP_KERNEL  
  14.     . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);  
  15. #else  
  16.     . = PAGE_OFFSET + TEXT_OFFSET;  
  17. #endif  
  18. }  
入口函数是head.S中的stext,不采用XIP技术,入口地址是PAGE_OFFSET+TEXT_OFFSET。
./arch/arm/include/asm/memory.h中:
[plain] view plain copy
  1. #define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)  
  2. Menuconfig中CONFIG_PAGE_OFFSET = 0xc0000000  
  3. ./arch/arm/Makefile中:  
  4. textofs-y   := 0x00008000  
  5. textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000  
  6. # We don't want the htc bootloader to corrupt kernel during resume  
  7. textofs-$(CONFIG_PM_H1940)      := 0x00108000  
  8. # SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory  
  9. ifeq ($(CONFIG_ARCH_SA1100),y)  
  10. textofs-$(CONFIG_SA1111) := 0x00208000  
  11. endif  
  12. textofs-$(CONFIG_ARCH_MSM7X30) := 0x00208000  
  13. textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000  
  14. textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000  
  15. ......  
  16. # The byte offset of the kernel image in RAM from the start of RAM.  
  17. TEXT_OFFSET := $(textofs-y)  
入口地址是0xc0008000.
但是实际操作中,kernel是加载到0x80008000地址运行的。
(我使用处理器sdram物理起始地址是0x80000000)


为什么链接地址和运行地址不一致?

学习完start_kernel之前的汇编,就会明白原因了。

在stext中,首先调用到__lookup_processor_type,Kernel代码将所有CPU信息的定义都放到.proc.info.init段中,因此可以认为.proc.info.init段就是一个数组,每个元素都定义了一个或一种CPU的信息。

目前__lookup_processor_type使用该元素的前两个字段cpuid和mask来匹配当前CPUID,如果满足CPUID & mask == cpuid,则找到当前cpu的定义并返回。 

代码如下:

[plain] view plain copy
  1.    __CPUINIT  
  2. __lookup_processor_type:  
  3.     //3行汇编,计算出物理地址与虚拟地址之间的offset,存在r3中  
  4.     adr r3, __lookup_processor_type_data  
  5.     ldmia   r3, {r4 - r6}  
  6.     sub r3, r3, r4          @ get offset between virt&phys  
  7.     //获取__proc_info_begin的物理地址  
  8.     add r5, r5, r3          @ convert virt addresses to  
  9.     //获取__proc_info_end的物理地址  
  10.     add r6, r6, r3          @ physical address space  
  11.     //mask cp15读出的cpuid,与proc_type_list中value对比  
  12. 1:  ldmia   r5, {r3, r4}            @ value, mask  
  13.     and r4, r4, r9          @ mask wanted bits  
  14.     teq r3, r4  
  15.     //一致则返回,不一致则跳到下一个proc_type_list,继续对比  
  16.     beq 2f  
  17.     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)  
  18.     cmp r5, r6  
  19.     blo 1b  
  20.     //匹配成功,r5存该proc_type_list指针,匹配失败,r5置0  
  21.     mov r5, #0              @ unknown processor  
  22. 2:  mov pc, lr  
  23. ENDPROC(__lookup_processor_type)  
  24.   
  25. /*  
  26.  * Look in <asm/procinfo.h> for information about the __proc_info structure.  
  27.  */  
  28.     .align  2  
  29.     .type   __lookup_processor_type_data, %object  
  30. __lookup_processor_type_data:  
  31.     .long   .  
  32.     .long   __proc_info_begin  
  33.     .long   __proc_info_end  
  34.     .size   __lookup_processor_type_data, . - __lookup_processor_type_data</span>  


因为kernel要开启MMU,所以kernel编译链接地址是虚拟地址(物理地址经过MMU转换后CPU看到的地址),并不是物理地址,

 链接确定了变量的绝对地址(虚拟地址),但在现阶段,没开启MMU,CPU看到的sdram地址就是其物理地址(0x80000000起始)。
 如果直接运行,对于变量的寻址则会出现问题(函数寻址没问题,因为arm函数寻址使用相对跳转指令b bl)
 比如,kernel image中全局变量i链接地址在0xc0009000,但现阶段i物理地址是在0x80009000,对于CPU来说,只能在0x80009000上才能找到i。
 去0xc0009000寻址,程序运行就出错了。
 这就是为什么我们所理解的,链接地址 加载地址 运行地址必须一致的原因。


 kernel现阶段给出的解决方法,就是lookup_processor_type前3行汇编:

adr r3, __lookup_processor_type_data 加载__lookup_processor_type_data地址(实际运行地址,这里就是物理地址)到r3

ldmia r3, {r4 - r6} 获取以r3 r3+4 r3+8为地址的变量到r4,r5,r6.
地址变量值是在链接时确定的,所以r4中存的是__lookup_processor_type_data的链接地址(虚拟地址)。

sub r3 ,r3 ,r4     r3中存储的是物理地址与虚拟地址的偏移。


这是多么genius的操作啊!

_proc_info_begin _proc_info_end在链接脚本中定义,是.proc.info.init段的首尾。
该段中是proc_info_list struct,表示处理器相关信息,定义如下:

[plain] view plain copy
  1. struct proc_info_list {  
  2.     unsigned int        cpu_val;  
  3.     unsigned int        cpu_mask;  
  4.     unsigned long       __cpu_mm_mmu_flags; /* used by head.S */  
  5.     unsigned long       __cpu_io_mmu_flags; /* used by head.S */  
  6.     unsigned long       __cpu_flush;        /* used by head.S */  
  7.     const char      *arch_name;  
  8.     const char      *elf_name;  
  9.     unsigned int        elf_hwcap;  
  10.     const char      *cpu_name;  
  11.     struct processor    *proc;  
  12.     struct cpu_tlb_fns  *tlb;  
  13.     struct cpu_user_fns *user;  
  14.     struct cpu_cache_fns    *cache;  
  15. };  


该段是在arch/arm/mm/proc-xxx.S中填充,定义了对应arm指令集的处理器特性和初始化函数,在第三篇文章中我们还会详细来理解proc info的作用,这里先按下不表。

lookup_processor_type_data返回stext中。
接下来同样用上面的方法获取phy&virt offset,存在r8.


根据我之前分析uboot传参kernel的博文(链接如下:http://blog.csdn.NET/skyflying2012/article/details/35787971)

r1存储machine id,r2存储atags。

stext中__vet_atags会对atags做一个基本的检查,代码如下:


[plain] view plain copy
  1. __vet_atags:  
  2.     tst r2, #0x3            @ aligned?  
  3.     bne 1f  
  4.   
  5.     ldr r5, [r2, #0]  
  6.     //判断是否是dtb类型  
  7. #ifdef CONFIG_OF_FLATTREE  
  8.     ldr r6, =OF_DT_MAGIC        @ is it a DTB?  
  9.     cmp r5, r6  
  10.     beq 2f  
  11. #endif  
  12.     cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?  
  13.     cmpne   r5, #ATAG_CORE_SIZE_EMPTY  
  14.     bne 1f  
  15.     ldr r5, [r2, #4]  
  16.     ldr r6, =ATAG_CORE  
  17.     cmp r5, r6  
  18.     bne 1f  
  19.   
  20.     //正确tags,返回  
  21. 2:  mov pc, lr              @ atag/dtb pointer is ok  
  22.   
  23.     //错误tags,清空r2,返回  
  24. 1:  mov r2, #0  
  25.     mov pc, lr  
  26. ENDPROC(__vet_atags)  


检查tag头4 byte(tag_core的size)和第二个4 byte(tag_core的type)是否正确。


对于stext中前几十行汇编,已经分析完成,总结下做了哪些工作:
(1)设置CPU模式
(2)检查CPUID是否匹配
(3)获取phy&virt offset
(4)检查atags参数


这段代码就分析到这,不过引起了我对于链接地址 运行地址的思考。
教科书上是这样教的,我也一直这么认为,链接地址 运行地址(加载地址)必须是一致,但是却没有真正去思考过为什么。


程序的链接地址与运行地址为什么要一致?

我的理解,链接确定程序运行绝对地址,也确定了其中变量及函数的绝对地址,加载运行地址不是其链接地址,变量实际存储的地址就变了。
这时如果对变量进行寻址,就会有不可知的结果,这是我能想到的原因。
平时我们编译链接都是一些C语言编写程序,难免会定义一些全局变量,如果链接和运行地址不一致,就不能正常寻址。

如果想运行和链接地址不一致,我能想到的办法,只能是汇编中尽量不去涉及一些绝对地址,使用PIC位置无关代码。

联想之前分析的uboot relocation原理(博文链接:http://blog.csdn.net/skyflying2012/article/details/37660265),

uboot在relocation之后,kernel在开启MMU之前,都实现了链接地址和运行地址不一致,看看它们用的什么方法?


(1)uboot在relocation时修改rel.dyn段(存储所有变量地址),实现将所有变量地址重定位到新运行地址


(2)kernel在开启MMU之前,计算运行地址(物理地址)与链接地址(虚拟地址)的偏移,对变量寻址时都进行地址转换,从而正常找到变量。开启MMU之后,利用硬件机制,来实现链接和运行地址的统一


所以说,链接地址一定要等于运行地址吗?不一定,嵌入式最著名的uboot  kernel就是例子!

                                          arm-linux内核start_kernel之前启动分析(2)- 页表的准备

arm-Linux内核start_kernel之前启动分析另外2篇博文链接地址如下:

http://blog.csdn.net/skyflying2012/article/details/41344377

http://blog.csdn.net/skyflying2012/article/details/48054417


今天接着第一篇继续分析,不过今天只分析stext中一条汇编,如下:

    bl  __create_page_tables

kernel版本:3.4.55

看看kernel启动初期,开启MMU之前如何初始化页表。此处分析过程我都写在对应的代码处,方便查看。


[cpp] view plain copy
  1. #ifdef CONFIG_ARM_LPAE  
  2.     /* LPAE requires an additional page for the PGD */  
  3. #define PG_DIR_SIZE 0x5000  
  4. #define PMD_ORDER   3  
  5. #else  
  6. #define PG_DIR_SIZE 0x4000  
  7. #define PMD_ORDER   2  
  8. #endif  
  9. .....  
  10.     .macro  pgtbl, rd, phys  
  11.     add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE  
  12.     .endm  
  13. .....  
  14. __create_page_tables:  
  15.     //据上篇博文分析,r8存储着sdram的物理起始地址(我的板子0x80000000)  
  16.     //pgtbl宏获取0x80008000之下16K的地址空间作为页表空间  
  17.     //arm页表一页是4 bytes,完成虚拟地址空间4GB中1MB的映射,  
  18.     //一共需要4 x 4096 bytes的页表空间  
  19.     //可以看出,单页完成的是虚拟地址和物理地址高12位的转换。  
  20.     //低20位的地址(1M内的地址)偏移是一致的。  
  21.     pgtbl   r4, r8              @ page table address  
  22.     /* 
  23.      * Clear the swapper page table 
  24.      */  
  25.     //按照16bytes一页将16K页表空间清空  
  26.     mov r0, r4  
  27.     mov r3, #0  
  28.     add r6, r0, #PG_DIR_SIZE  
  29. 1:  str r3, [r0], #4  
  30.     str r3, [r0], #4  
  31.     str r3, [r0], #4  
  32.     str r3, [r0], #4  
  33.     teq r0, r6  
  34.     bne 1b  
  35.   
  36.     //如果定义CONFIG_ARM_LPAE,在PGD与PMD之前还要再加一级页表,这里不详解这种情景  
  37. #ifdef CONFIG_ARM_LPAE  
  38.     /* 
  39.      * Build the PGD table (first level) to point to the PMD table. A PGD 
  40.      * entry is 64-bit wide. 
  41.      */  
  42.     mov r0, r4  
  43.     add r3, r4, #0x1000         @ first PMD table address  
  44.     orr r3, r3, #3          @ PGD block type  
  45.     mov r6, #4              @ PTRS_PER_PGD  
  46.     mov r7, #1 << (55 - 32)     @ L_PGD_SWAPPER  
  47. 1:  str r3, [r0], #4            @ set bottom PGD entry bits  
  48.     str r7, [r0], #4            @ set top PGD entry bits  
  49.     add r3, r3, #0x1000         @ next PMD table  
  50.     subs    r6, r6, #1  
  51.     bne 1b  
  52.   
  53.     add r4, r4, #0x1000         @ point to the PMD tables  
  54. #endif  
  55.     //据上篇博文分析,r10中存储该CPU的processor_type_list(处理器信息结构体),获取该CPU的mmuflags  
  56.     ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags  
  57.   
  58.     /* 
  59.      * Create identity mapping to cater for __enable_mmu. 
  60.      * This identity mapping will be removed by paging_init(). 
  61.      */  
  62.     //首先建立包含turn_mmu_on函数1M空间的平映射(virt addr = phy addr)  
  63.     //turn_mmu_on距stext不远,所以实际完成0x8000000-0x81000000空间的平映射  
  64.       
  65.     //老方法,上篇博文分析过,获取phy到virt的offset  
  66.     adr r0, __turn_mmu_on_loc  
  67.     ldmia   r0, {r3, r5, r6}  
  68.     sub r0, r0, r3          @ virt->phys offset  
  69.     //获取turn_mmu_on的首尾物理地址  
  70.     add r5, r5, r0          @ phys __turn_mmu_on  
  71.     add r6, r6, r0          @ phys __turn_mmu_on_end  
  72.     //因1页映射1M空间,所以SECTION_SHIFT为20  
  73.     //右移20位后,r5,r6代表该段地址空间的物理地址页号,因为是平映射,也代表了页表中的须知下标,即虚拟地址页号!  
  74.     mov r5, r5, lsr #SECTION_SHIFT  
  75.     mov r6, r6, lsr #SECTION_SHIFT  
  76.   
  77.     //r5左移20位,获取该页基地址,或上CPU的mmuflags,存在r3中  
  78. 1:  orr r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel base  
  79.     //将r3值存储在页表空间(r4起始)的(r5<<4)的页表中  
  80.     //因一页用4bytes表示,所以PMD_ORDER=2  
  81.     str r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping  
  82.     //r5与r6之前相距多个1M,则需要填写多个页表。  
  83.     //因turn_mmu_on函数很短,所以肯定在1M内,该处r5=r6  
  84.     cmp r5, r6  
  85.     addlo   r5, r5, #1          @ next section  
  86.     blo 1b  
  87.     //从上面这次填页表的过程可以看出,16KB的页表以虚拟地址页号为寻址下标,覆盖整个虚拟的4G地址空间  
  88.   
  89.     /* 
  90.      * Now setup the pagetables for our kernel direct 
  91.      * mapped region. 
  92.      */  
  93.     //接下来以多个1M的线性映射页表,建立kernel整个镜像的线性映射,((0x80000000-0x80000000+kernel_end)-(0xc0000000-0xc0000000+kernel_end))  
  94.     //开启MMU之后就实现了链接地址(0xc0008000)与运行地址(0xc0008000)的统一  
  95.       
  96.     //这里有一个小技巧,利用当前PC值作为内核物理地址起始,create_page_tables距离内核起始地址不超过1MB,因此移位之后就是内核起始的物理页号。  
  97.     //arm的create_page_tables中,不管是turn_mmu_on还是这里,都是使用的当前pc值计算物理页号,  
  98.     //这样的好处是,不管内核加载到什么物理地址,都可以迅速的建立正确的页表映射。并且不需要内核开发人员对这部分代码进行修改  
  99.     mov r3, pc  
  100.     mov r3, r3, lsr #SECTION_SHIFT  
  101.     orr r3, r7, r3, lsl #SECTION_SHIFT  
  102.     //将该1M空间的物理起始地址存储到页表中相应虚拟地址页中  
  103.     add r0, r4,  #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)  
  104.     str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!  
  105.     ldr r6, =(KERNEL_END - 1)  
  106.     add r0, r0, #1 << PMD_ORDER  
  107.     add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)  
  108. 1:  cmp r0, r6  
  109.     add r3, r3, #1 << SECTION_SHIFT  
  110.     strls   r3, [r0], #1 << PMD_ORDER  
  111.     bls 1b  
  112. #ifdef CONFIG_XIP_KERNEL  
  113.     /* 
  114.      * Map some ram to cover our .data and .bss areas. 
  115.      */  
  116.     add r3, r8, #TEXT_OFFSET  
  117.     orr r3, r3, r7  
  118.     add r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)  
  119.     str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> (SECTION_SHIFT - PMD_ORDER)]!  
  120.     ldr r6, =(_end - 1)  
  121.     add r0, r0, #4  
  122.     add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)  
  123. 1:  cmp r0, r6  
  124.     add r3, r3, #1 << 20  
  125.     strls   r3, [r0], #4  
  126.     bls 1b  
  127. #endif  
  128.   
  129.     /* 
  130.      * Then map boot params address in r2 or the first 1MB (2MB with LPAE) 
  131.      * of ram if boot params address is not specified. 
  132.      */  
  133.     //将atags的1M地址空间做线性映射,方便start_kernel中对args进行分析  
  134.     //据上篇博文分析,r2中存储着bootloader传来的atag基地址(我的板子在0x80000100)  
  135.     //所以该1M空间是0x80000000-0x81000000,映射到0xc0000000-0xc1000000  
  136.     mov r0, r2, lsr #SECTION_SHIFT  
  137.     movs    r0, r0, lsl #SECTION_SHIFT  
  138.     moveq   r0, r8  
  139.     sub r3, r0, r8  
  140.     add r3, r3, #PAGE_OFFSET  
  141.     add r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)  
  142.     orr r6, r7, r0  
  143.     str r6, [r3]  
  144.   
  145. //如果需要早期串口输出进行调试,在这里进行I/O空间的映射,从而实现可以对串口控制器的操作,这里不详解了。  
  146. #ifdef CONFIG_DEBUG_LL  
  147. #if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)  
  148.     /* 
  149.      * Map in IO space for serial debugging. 
  150.      * This allows debug messages to be output 
  151.      * via a serial console before paging_init. 
  152.      */  
  153.     addruart r7, r3, r0  
  154.   
  155.     mov r3, r3, lsr #SECTION_SHIFT  
  156.     mov r3, r3, lsl #PMD_ORDER  
  157.   
  158.     add r0, r4, r3  
  159.     rsb r3, r3, #0x4000         @ PTRS_PER_PGD*sizeof(long)  
  160.     cmp r3, #0x0800         @ limit to 512MB  
  161.     movhi   r3, #0x0800  
  162.     add r6, r0, r3  
  163.     mov r3, r7, lsr #SECTION_SHIFT  
  164.     ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags  
  165.     orr r3, r7, r3, lsl #SECTION_SHIFT  
  166. #ifdef CONFIG_ARM_LPAE  
  167.     mov r7, #1 << (54 - 32)     @ XN  
  168. #else  
  169.     orr r3, r3, #PMD_SECT_XN  
  170. #endif  
  171. 1:  str r3, [r0], #4  
  172. #ifdef CONFIG_ARM_LPAE  
  173.     str r7, [r0], #4  
  174. #endif  
  175.     add r3, r3, #1 << SECTION_SHIFT  
  176.     cmp r0, r6  
  177.     blo 1b  
  178.   
  179. #else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */  
  180.     /* we don't need any serial debugging mappings */  
  181.     ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags  
  182. #endif  
  183. #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)  
  184.     /* 
  185.      * If we're using the NetWinder or CATS, we also need to map 
  186.      * in the 16550-type serial port for the debug messages 
  187.      */  
  188.     add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)  
  189.     orr r3, r7, #0x7c000000  
  190.     str r3, [r0]  
  191. #endif  
  192. #ifdef CONFIG_ARCH_RPC  
  193.     /* 
  194.      * Map in screen at 0x02000000 & SCREEN2_BASE 
  195.      * Similar reasons here - for debug.  This is 
  196.      * only for Acorn RiscPC architectures. 
  197.      */  
  198.     add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)  
  199.     orr r3, r7, #0x02000000  
  200.     str r3, [r0]  
  201.     add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)  
  202.     str r3, [r0]  
  203. #endif  
  204. #endif  
  205. #ifdef CONFIG_ARM_LPAE  
  206.     sub r4, r4, #0x1000     @ point to the PGD table  
  207. #endif  
  208.     mov pc, lr  
  209. ENDPROC(__create_page_tables)  
  210.     .ltorg  
  211.     .align  
  212. __turn_mmu_on_loc:  
  213.     .long   .  
  214.     .long   __turn_mmu_on  
  215.     .long   __turn_mmu_on_end  




create_page_table完成了3种地址映射的页表空间填写:

(1)turn_mmu_on所在1M空间的平映射
(2)kernel image的线性映射
(2)atags所在1M空间的线性映射


物理地址空间和虚拟地址空间映射关系图如下:



对于这3种地址空间映射,我觉得分别有3个值得思考的地方:


(1)为什么turn_mmu_on要做平映射?
turn_mmu_on我会在下一篇博文中分析,主要是完成开启MMU的操作。
那为什么将turn_mmu_on处做一个平映射?
可以想象,执行开启MMU指令之前,CPU取指是在0x80008000附近turn_mmu_on中。
如果只是做kernel image的线性映射,执行开启MMU指令后,CPU所看到的地址就全变啦。
turn_mmu_on对于CPU来说在0xc0008000附近,0x80008000附近对于CPU来说已经不可预知了。
但是CPU不知道这些,它只管按照地址一条条取指令,执行指令。
所以不做turn_mmu_on的平映射(virt addr = phy addr),turn_mmu_on在开启MMU后的运行是完全不可知。
完成turn_mmu_on的平映射,我们可以在turn_mmu_on末尾MMU已经开启稳定后,修改PC到0xc0008000附近,就可以解决从0x8xxxxxxx到0xcxxxxxxx的跳转。

(2)kernel image加载地址为什么会在0x****8000?
分析了kernel image线性映射部分,这个就好理解了,
kernel编译链接时的入口地址在0xc0008000(PAGE_OFFSET + TEXT_OFFSET),但其物理地址不等于其链接的虚拟地址,image的线性映射实现其运行地址等于链接地址。
kernel的每一页表映射1M,所以入口处在(0x80000000-->0xc0000000)映射页表中完成映射。物理地址和虚拟地址的1M内偏移必须一致呀。
kernel定义的TEXT_OFFSET = 0x8000.所以加载的物理地址必须为0x****8000.
这样,开启MMU后,访问0xc0008000附近指令,MMU根据TLB才能正确映射找到0x****8000附近的指令。

(3)atags跟kernel入口是在同一1M空间内,bootparams的线性映射操作是否多余?
根据第二个问题的分析,kernel image可以加载到任何sdram地址空间的0x****8000即可。
atags地址是有bootloader中指定,然后告诉kernel。
那就有这样一种情况,加入sdram起始地址为0x80000000,atags起始地址为0x80000100。
但kernel image我加载到0x81008000,可以看出,这时atags跟kernel image就在不同一1M空间啦
atags单独的线性映射操作还是很有必要的。


这是我想到的关于create_page_table的3个疑问,大家如果有别的疑问,欢迎留言讨论,共同学习。


今天就分析到这,页表准备就绪,只待开启MMU!

            arm-linux内核start_kernel之前启动分析(3)-开启MMU,走进新时代

最近在忙一款PPC处理器的芯片验证和内核移植工作,导致arm-Linux启动分析最后一部一直没有写,今天将arm-linux start_kernel之前的最后一部分分析记录下。之前2篇文章链接如下:
http://blog.csdn.net/skyflying2012/article/details/41344377
http://blog.csdn.net/skyflying2012/article/details/41447843

kernel版本号:3.4.55

之前分析到__create_page_tables在内核代码区TEXT_OFF下部的16KB区域内进行页表的配置,完成turn_mmu_on的平映射以及kernel image的线性映射。接下来就需要开启MMU,让整个CPU进入虚拟地址运行的新阶段。head.S中stext最后一段代码如下:

   /*     * The following calls CPU specific code in a position independent     * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of     * xxx_proc_info structure selected by __lookup_processor_type     * above.  On return, the CPU will be ready for the MMU to be     * turned on, and r0 will hold the CPU control register value.     */    ldr r13, =__mmap_switched       @ address to jump to after                        @ mmu has been enabled    adr lr, BSYM(1f)            @ return (PIC) address    mov r8, r4              @ set TTBR1 to swapper_pg_dir ARM(   add pc, r10, #PROCINFO_INITFUNC ) THUMB( add r12, r10, #PROCINFO_INITFUNC    ) THUMB( mov pc, r12             )1:  b   __enable_mmuENDPROC(stext)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

看注释也可以明白接下来要完成的2件工作:执行CPU特定处理代码,开启MMU。
在第一篇分析中我们知道r10中存储着本CPU的proc_info_list首地址。到这里需要再详细解释下内核的proc
info机制。在kernel image中定义有一个.proc.info.init的段。在arch/arm/kernel/vmlinux.lds.S中,如下:

#define PROC_INFO                           \    . = ALIGN(4);                           \    VMLINUX_SYMBOL(__proc_info_begin) = .;              \    *(.proc.info.init)                      \    VMLINUX_SYMBOL(__proc_info_end) = .;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

__proc_info_begin和__pro_info_end分别代表该段的头尾。.proc.info.init段中存储的数据是在arch/arm/mm/proc-xxx.S中定义的。到底使用哪个proc-xxx.S则由处理器的指令集版本号决定。
以我的cortex-A8处理器为例,是armv7指令集,根据arch/arm/mm/Makefile。

obj-$(CONFIG_CPU_V6)        += proc-v6.oobj-$(CONFIG_CPU_V6K)       += proc-v6.oobj-$(CONFIG_CPU_V7)        += proc-v7.o
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

编译的是proc-v7.S,在该文件中有如下一段汇编:

   .section ".rodata"    string  cpu_arch_name, "armv7"    string  cpu_elf_name, "v7"    .align    //接下来定义的数据都在.proc.info.init段中    .section ".proc.info.init", #alloc, #execinstr    /*     * Standard v7 proc info content     */    //定义了宏定义__v7_proc,这个宏定义非常重要!.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0    ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \            PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)    ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \            PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)    .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \        PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags    W(b)    \initfunc    .long   cpu_arch_name    .long   cpu_elf_name    .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \        HWCAP_EDSP | HWCAP_TLS | \hwcaps    .long   cpu_v7_name    .long   v7_processor_functions    .long   v7wbi_tlb_fns    .long   v6_user_fns    .long   v7_cache_fns.endm     //没有选择LPAE#ifndef CONFIG_ARM_LPAE    /*     * ARM Ltd. Cortex A5 processor.     */    .type   __v7_ca5mp_proc_info, #object__v7_ca5mp_proc_info:    .long   0x410fc050    .long   0xff0ffff0    __v7_proc __v7_ca5mp_setup    .size   __v7_ca5mp_proc_info, . - __v7_ca5mp_proc_info    /*     * ARM Ltd. Cortex A9 processor.     */    .type   __v7_ca9mp_proc_info, #object__v7_ca9mp_proc_info:    .long   0x410fc090    .long   0xff0ffff0    __v7_proc __v7_ca9mp_setup    .size   __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info#endif  /* CONFIG_ARM_LPAE */    /*     * ARM Ltd. Cortex A7 processor.     */    .type   __v7_ca7mp_proc_info, #object__v7_ca7mp_proc_info:    .long   0x410fc070    .long   0xff0ffff0    __v7_proc __v7_ca7mp_setup, hwcaps = HWCAP_IDIV    .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info    /*     * ARM Ltd. Cortex A15 processor.     */    .type   __v7_ca15mp_proc_info, #object__v7_ca15mp_proc_info:    .long   0x410fc0f0    .long   0xff0ffff0    __v7_proc __v7_ca15mp_setup, hwcaps = HWCAP_IDIV    .size   __v7_ca15mp_proc_info, . - __v7_ca15mp_proc_info    /*     * Match any ARMv7 processor core.     */    .type   __v7_proc_info, #object__v7_proc_info:    .long   0x000f0000      @ Required ID value    .long   0x000f0000      @ Mask for ID    __v7_proc __v7_setup    .size   __v7_proc_info, . - __v7_proc_info
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

这段汇编看出,指定.proc.info.init段中存储的是一些结构体,定义了V7指令集特定处理器的属性和处理函数。在C文件中我们找到了这些结构体的定义,在arch/arm/include/asm/proc-info.h中:

/* * Note!  struct processor is always defined if we're * using MULTI_CPU, otherwise this entry is unused, * but still exists. * * NOTE! The following structure is defined by assembly * language, NOT C code.  For more information, check: *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S */struct proc_info_list {    unsigned int        cpu_val;    unsigned int        cpu_mask;    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */    unsigned long       __cpu_io_mmu_flags; /* used by head.S */    unsigned long       __cpu_flush;        /* used by head.S */    const char      *arch_name;    const char      *elf_name;    unsigned int        elf_hwcap;    const char      *cpu_name;    struct processor    *proc;    struct cpu_tlb_fns  *tlb;    struct cpu_user_fns *user;    struct cpu_cache_fns    *cache;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

proc-v7.S中定义的__v7_ca5mp_proc_info __v7_ca9mp_proc_info __v7_ca7mp_proc_info __v7_ca15mp_proc_info __v7_proc_info都是proc_info_list结构体,分别对应A5 A9 A7
A15以及其他v7指令集处理器,这些结构体定义时都使用了__v7_proc宏定义来完成成员变量赋值。
为了验证这些proc_info_list结构体都在.proc.info.init段中,我对编译生成的vmlinux的.proc.info.init段进行反汇编。如下:

#arm-linux-objdump -dS --section=.proc.info.init vmlinuxvmlinux:     file format elf32-littlearmDisassembly of section .init.proc.info:c03d98cc <__proc_info_begin>:c03d98cc:   410fc050    .word   0x410fc050c03d98d0:   ff0ffff0    .word   0xff0ffff0c03d98d4:   00000c0e    .word   0x00000c0ec03d98d8:   00000c02    .word   0x00000c02     */    .type   __v7_ca5mp_proc_info, #object__v7_ca5mp_proc_info:    .long   0x410fc050    .long   0xff0ffff0    __v7_proc __v7_ca5mp_setupc03d98dc:   eafff872    b   c03d7aac <__v7_ca5mp_setup>c03d98e0:   c02f07a4    .word   0xc02f07a4c03d98e4:   c02f07aa    .word   0xc02f07aac03d98e8:   00008097    .word   0x00008097c03d98ec:   c0017b98    .word   0xc0017b98c03d98f0:   c03db2c0    .word   0xc03db2c0c03d98f4:   c03c52c8    .word   0xc03c52c8c03d98f8:   c03db2b8    .word   0xc03db2b8c03d98fc:   c03db290    .word   0xc03db290c03d9900 <__v7_ca9mp_proc_info>:c03d9900:   410fc090 ff0ffff0 00000c0e 00000c02     ...A............     */    .type   __v7_ca9mp_proc_info, #object__v7_ca9mp_proc_info:    .long   0x410fc090    .long   0xff0ffff0    __v7_proc __v7_ca9mp_setupc03d9910:   eafff865 c02f07a4 c02f07aa 00008097     e...../.../.....c03d9920:   c0017b98 c03db2c0 c03c52c8 c03db2b8     .{....=..R<...=.c03d9930:   c03db290                                ..=.c03d9934 <__v7_ca7mp_proc_info>:c03d9934:   410fc070 ff0ffff0 00000c0e 00000c02     p..A............     */    .type   __v7_ca7mp_proc_info, #object__v7_ca7mp_proc_info:    .long   0x410fc070    .long   0xff0ffff0    __v7_proc __v7_ca7mp_setup, hwcaps = HWCAP_IDIVc03d9944:   eafff85a c02f07a4 c02f07aa 00068097     Z...../.../.....c03d9954:   c0017b98 c03db2c0 c03c52c8 c03db2b8     .{....=..R<...=.c03d9964:   c03db290                                ..=.c03d9968 <__v7_ca15mp_proc_info>:c03d9968:   410fc0f0 ff0ffff0 00000c0e 00000c02     ...A............     */    .type   __v7_ca15mp_proc_info, #object__v7_ca15mp_proc_info:    .long   0x410fc0f0    .long   0xff0ffff0    __v7_proc __v7_ca15mp_setup, hwcaps = HWCAP_IDIVc03d9978:   eafff84d c02f07a4 c02f07aa 00068097     M...../.../.....c03d9988:   c0017b98 c03db2c0 c03c52c8 c03db2b8     .{....=..R<...=.c03d9998:   c03db290                                ..=.c03d999c <__v7_proc_info>:c03d999c:   000f0000 000f0000 00000c0e 00000c02     ................     */    .type   __v7_proc_info, #object__v7_proc_info:    .long   0x000f0000      @ Required ID value    .long   0x000f0000      @ Mask for ID    __v7_proc __v7_setupc03d99ac:   eafff841 c02f07a4 c02f07aa 00008097     A...../.../.....c03d99bc:   c0017b98 c03db2c0 c03c52c8 c03db2b8     .{....=..R<...=.c03d99cc:   c03db290                                ..=.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

很显然,跟我们分析的一样,.proc.info.init段中存储的就是proc-v7.S中定义的proc_info_list结构体。
第一篇分析中指出__lookup_processor_type函数即遍历.proc.info.init段,根据CP15
c0寄存器读到的处理器版本号进行匹配。将匹配到的proc_info_list结构体存在r10中。
根据实际的运行情况,我的A8处理器遍历后匹配到的是__v7_proc_info,因此r10寄存器存储的是__v7_proc_info结构体首地址。
.proc.info.init段先解释到这里,回到stext中,r13中存下__mmap_switched地址,需要注意的是该地址是链接地址,第二篇说过现在整个kernel image还是运行在物理地址上,因此该地址跳转必须等到开启MMU之后了。接下来对于ARM指令(不是THUMB指令),跳转到__v7_proc_info->__cpu_flush执行。
proc-v7.S中分析过,__v7_proc_info中使用__v7_proc宏定义定义了其中的一些成员变量,其中__cpu_flush=__v7_setup.__v7_setup在proc-v7.S中定义如下:

__v7_setup:    adr r12, __v7_setup_stack       @ the local stack    stmia   r12, {r0-r5, r7, r9, r11, lr}    bl  v7_flush_dcache_all    ldmia   r12, {r0-r5, r7, r9, r11, lr}    mrc p15, 0, r0, c0, c0, 0       @ read main ID register    and r10, r0, #0xff000000        @ ARM?    teq r10, #0x41000000    bne 3f    and r5, r0, #0x00f00000     @ variant    and r6, r0, #0x0000000f     @ revision    orr r6, r6, r5, lsr #20-4       @ combine variant and revision    ubfx    r0, r0, #4, #12         @ primary part number    /* Cortex-A8 Errata */    ldr r10, =0x00000c08        @ Cortex-A8 primary part number    teq r0, r10    bne 2f#ifdef CONFIG_ARM_ERRATA_430973    teq r5, #0x00100000         @ only present in r1p*    mrceq   p15, 0, r10, c1, c0, 1      @ read aux control register    orreq   r10, r10, #(1 << 6)     @ set IBE to 1    mcreq   p15, 0, r10, c1, c0, 1      @ write aux control register#endif#ifdef CONFIG_ARM_ERRATA_458693    teq r6, #0x20           @ only present in r2p0    mrceq   p15, 0, r10, c1, c0, 1      @ read aux control register    orreq   r10, r10, #(1 << 5)     @ set L1NEON to 1    orreq   r10, r10, #(1 << 9)     @ set PLDNOP to 1    mcreq   p15, 0, r10, c1, c0, 1      @ write aux control register#endif#ifdef CONFIG_ARM_ERRATA_460075    teq r6, #0x20           @ only present in r2p0    mrceq   p15, 1, r10, c9, c0, 2      @ read L2 cache aux ctrl register    tsteq   r10, #1 << 22    orreq   r10, r10, #(1 << 22)        @ set the Write Allocate disable bit    mcreq   p15, 1, r10, c9, c0, 2      @ write the L2 cache aux ctrl register#endif    b   3f    /* Cortex-A9 Errata */2:  ldr r10, =0x00000c09        @ Cortex-A9 primary part number    teq r0, r10    bne 3f#ifdef CONFIG_ARM_ERRATA_742230    cmp r6, #0x22           @ only present up to r2p2    mrcle   p15, 0, r10, c15, c0, 1     @ read diagnostic register    orrle   r10, r10, #1 << 4       @ set bit #4    mcrle   p15, 0, r10, c15, c0, 1     @ write diagnostic register#endif#ifdef CONFIG_ARM_ERRATA_742231    teq r6, #0x20           @ present in r2p0    teqne   r6, #0x21           @ present in r2p1    teqne   r6, #0x22           @ present in r2p2    mrceq   p15, 0, r10, c15, c0, 1     @ read diagnostic register    orreq   r10, r10, #1 << 12      @ set bit #12    orreq   r10, r10, #1 << 22      @ set bit #22    mcreq   p15, 0, r10, c15, c0, 1     @ write diagnostic register#endif#ifdef CONFIG_ARM_ERRATA_743622    teq r5, #0x00200000         @ only present in r2p*    mrceq   p15, 0, r10, c15, c0, 1     @ read diagnostic register    orreq   r10, r10, #1 << 6       @ set bit #6    mcreq   p15, 0, r10, c15, c0, 1     @ write diagnostic register#endif#if defined(CONFIG_ARM_ERRATA_751472) && defined(CONFIG_SMP)    ALT_SMP(cmp r6, #0x30)          @ present prior to r3p0    ALT_UP_B(1f)    mrclt   p15, 0, r10, c15, c0, 1     @ read diagnostic register    orrlt   r10, r10, #1 << 11      @ set bit #11    mcrlt   p15, 0, r10, c15, c0, 1     @ write diagnostic register1:#endif3:  mov r10, #0    mcr p15, 0, r10, c7, c5, 0      @ I+BTB cache invalidate    dsb#ifdef CONFIG_MMU    mcr p15, 0, r10, c8, c7, 0      @ invalidate I + D TLBs    v7_ttb_setup r10, r4, r8, r5        @ TTBCR, TTBRx setup    ldr r5, =PRRR           @ PRRR    ldr r6, =NMRR           @ NMRR    mcr p15, 0, r5, c10, c2, 0      @ write PRRR    mcr p15, 0, r6, c10, c2, 1      @ write NMRR#endif#ifndef CONFIG_ARM_THUMBEE    mrc p15, 0, r0, c0, c1, 0       @ read ID_PFR0 for ThumbEE    and r0, r0, #(0xf << 12)        @ ThumbEE enabled field    teq r0, #(1 << 12)          @ check if ThumbEE is present    bne 1f    mov r5, #0    mcr p14, 6, r5, c1, c0, 0       @ Initialize TEEHBR to 0    mrc p14, 6, r0, c0, c0, 0       @ load TEECR    orr r0, r0, #1          @ set the 1st bit in order to    mcr p14, 6, r0, c0, c0, 0       @ stop userspace TEEHBR access1:#endif    adr r5, v7_crval    ldmia   r5, {r5, r6}#ifdef CONFIG_CPU_ENDIAN_BE8    orr r6, r6, #1 << 25        @ big-endian page tables#endif#ifdef CONFIG_SWP_EMULATE    orr     r5, r5, #(1 << 10)              @ set SW bit in "clear"    bic     r6, r6, #(1 << 10)              @ clear it in "mmuset"#endif    mrc p15, 0, r0, c1, c0, 0       @ read control register    bic r0, r0, r5          @ clear bits them    orr r0, r0, r6          @ set them THUMB( orr r0, r0, #1 << 30    )   @ Thumb exceptions    mov pc, lr              @ return to head.S:__retENDPROC(__v7_setup)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

初看到这段汇编肯定头大,但是仔细过一遍就会好些,大部分都是一些特定处理器的条件编译并且还有一些跳转label,很多代码可以忽略,对于A8处理器有用的部分我加粗标注出来了。完成的工作如下:
(1)刷新cache,v7_flush_dcache_all
(2)无效掉cache
(3)无效掉指令和数据TLB
(4)从CP15 c0寄存器读出到r0,进行处理,做为下一步写入CP15 c0的值
说实话这部分汇编内核开发者不需要修改,除非是你所用的处理器做了处理器核级的修改,我们只需要了解即可。这里最关心的是r0的数据,因为接下来写如CP15的c0寄存器启动MMU,值就是来自r0.
r0从CP15 c0获取后,首先清掉r5的置位,然后置位r6的置位,r5 r6来自于v7_crval前8个字节,v7_crval在proc-v7-2level.S和proc-v7-3level.S中分别由定义,到底使用哪个是在proc-info.S中条件编译指定的。

#ifdef CONFIG_ARM_LPAE#include "proc-v7-3level.S"#else#include "proc-v7-2level.S"#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我的A8处理器选择使用proc-v7-2level.S,v7_crval定义如下:

v7_crval:    crval   clear=0x0120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c
  • 1
  • 2
  • 1
  • 2

要明白这里定义的v7_crval值含义就需要看CP15 c0寄存器的定义,限于篇幅这里不再详细解释了,感兴趣的朋友可以看我的另一篇说明ARM CP15协处理器的博文:
http://blog.csdn.net/skyflying2012/article/details/25823967
__v7_setup执行完毕,回到stext,接下来跳转到__enable_mmu,也在head.S中,如下:

/* * Setup common bits before finally enabling the MMU.  Essentially * this is just loading the page table pointer and domain access * registers. * *  r0  = cp#15 control register *  r1  = machine ID *  r2  = atags or dtb pointer *  r4  = page table pointer *  r9  = processor ID *  r13 = *virtual* address to jump to upon completion */__enable_mmu:    /*     * turn on L2 cache     */    mov r5, #0x82    mcr p15, 1, r5, c9, c0, 2    mrc p15, 0, r5, c1, c0, 1    orr r5, r5, #CR_L2    mcr p15, 0, r5, c1, c0, 1#if defined(CONFIG_ALIGNMENT_TRAP) && __LINUX_ARM_ARCH__ < 6    orr r0, r0, #CR_A#else    bic r0, r0, #CR_A#endif#ifdef CONFIG_CPU_DCACHE_DISABLE    bic r0, r0, #CR_C#endif#ifdef CONFIG_CPU_BPREDICT_DISABLE    bic r0, r0, #CR_Z#endif#ifdef CONFIG_CPU_ICACHE_DISABLE    bic r0, r0, #CR_I#endif#ifdef CONFIG_ARM_LPAE    mov r5, #0    mcrr    p15, 0, r4, r5, c2      @ load TTBR0#else    mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \              domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \              domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \              domain_val(DOMAIN_IO, DOMAIN_CLIENT))    mcr p15, 0, r5, c3, c0, 0       @ load domain access register    mcr p15, 0, r4, c2, c0, 0       @ load page table pointer#endif    b   __turn_mmu_onENDPROC(__enable_mmu)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

该函数根据内核配置对CP15和r0寄存器进行配置,工作如下:
(1)操作CP15的c1寄存器打开L2 cache
(2)根据内核配置,关闭指令或者数据cache
(3)配置CP15 c3寄存器,配置ARM域的访问权限
(4)配置CP15 c2寄存器,指定内存页表地址

需要注意的是前面__v7_setup中只是无效掉cache,无效是cache操作一种,表示cache中数据无效了,下次由cache读取数据,则需要cache从内存中重新获取数据。这是保证cache数据一致性的手段。
接下来再次跳转到__turn_mmu_on执行,在head.S中如下:

/* * Enable the MMU.  This completely changes the structure of the visible * memory space.  You will not be able to trace execution through this. * If you have an enquiry about this, *please* check the linux-arm-kernel * mailing list archives BEFORE sending another post to the list. * *  r0  = cp#15 control register *  r1  = machine ID *  r2  = atags or dtb pointer *  r9  = processor ID *  r13 = *virtual* address to jump to upon completion * * other registers depend on the function called upon completion */    .align  5    .pushsection    .idmap.text, "ax"ENTRY(__turn_mmu_on)    mov r0, r0    instr_sync    mcr p15, 0, r0, c1, c0, 0       @ write control reg    mrc p15, 0, r3, c0, c0, 0       @ read id reg    instr_sync    mov r3, r3    mov r3, r13    mov pc, r3__turn_mmu_on_end:ENDPROC(__turn_mmu_on)    .popsection
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

该函数将r0值写入CP15的c0寄存器,查看CP15说明,c0寄存器的第0位表征MMU的开启,刚才__v7_setup中v7_crval给出的mmuset值已经将第0位置1了,因此写入c0后MMU就开启了!

在第二篇启动分析文章中讲到create pgtable时,对__turn_mmu_on所在的1M地址空间做了平映射,到现在这个阶段就看出其作用了。
CPU执行完成“mcr p15, 0, r0, c1, c0,0”指令后开启MMU,接下来CPU取指地址是当前pc+4,由于做了平映射,虽然接下来是虚拟地址,但是该虚拟地址跟之前的物理地址是完全一致的,pc+4可以取到mrc p15, 0, r3, c0, c0, 0指令,并不会导致CPU取指上的问题,
可以想象如果做的不是平映射,CPU取pc+4指令,该虚拟地址不是映射到mrc p15, 0, r3,c0, c0, 0指令所在物理地址,CPU接下来的执行就不可预测了。

开启MMU后,修改PC值为r13执行,也就是__mmap_switched,该函数已经是运行在MMU之上的虚拟地址了,因此不需要位置无关代码,如下:

/* * The following fragment of code is executed with the MMU on in MMU mode, * and uses absolute addresses; this is not position independent. * *  r0  = cp#15 control register *  r1  = machine ID *  r2  = atags/dtb pointer *  r9  = processor ID */    __INIT__mmap_switched:    adr r3, __mmap_switched_data    //拷贝data段    ldmia   r3!, {r4, r5, r6, r7}    cmp r4, r5              @ Copy data segment if needed1:  cmpne   r5, r6    ldrne   fp, [r4], #4    strne   fp, [r5], #4    bne 1b    //清空bss    mov fp, #0              @ Clear BSS (and zero fp)1:  cmp r6, r7    strcc   fp, [r6],#4    bcc 1b    //加载r3之后的5个int到r4-sp中 ARM(   ldmia   r3, {r4, r5, r6, r7, sp}) THUMB( ldmia   r3, {r4, r5, r6, r7}    ) THUMB( ldr sp, [r3, #16]       )    //保存数据到全局变量中    str r9, [r4]            @ Save processor ID    str r1, [r5]            @ Save machine type    str r2, [r6]            @ Save atags pointer    bic r4, r0, #CR_A           @ Clear 'A' bit    stmia   r7, {r0, r4}            @ Save control register values    b   start_kernelENDPROC(__mmap_switched)    .align  2    .type   __mmap_switched_data, %object__mmap_switched_data:    .long   __data_loc          @ r4    .long   _sdata              @ r5    .long   __bss_start         @ r6    .long   _end                @ r7    .long   processor_id            @ r4    .long   __machine_arch_type     @ r5    .long   __atags_pointer         @ r6    .long   cr_alignment            @ r7    .long   init_thread_union + THREAD_START_SP @ sp    .size   __mmap_switched_data, . - __mmap_switched_data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

__mmap_switched完成的工作如下:
(1)如果有必要则进行data段的拷贝搬移
(2)清空bss段
(3)保存处理器ID atag地址等到指定全局变量中,设置SP,跳转到start_kernel。
看到start_kernel说明我们的分析马上就要结束了,不过这里还是有2个地方需要思考下:
(1)全局变量赋值后,processor_id = r9,__machine_arch_type = r1, __atags_pointer = r2.
start_kernel中对atag等进行分析处理时直接对这些变量进行操作就可以。
(2)sp = init_thread_union + THREAD_START_SP,设置栈指针,为接下来的C函数运行做准备。对于arm-linux来说,start_kernel之前全部都是汇编代码。
init_thread_union代表的是内核第一个进程,pid = 0,该进程是内核人为造出来的,而不是fork出来的,在arch/arm/kernel/init_task.c中定义了该进程,

/* * Initial thread structure. * * We need to make sure that this is 8192-byte aligned due to the * way process stacks are handled. This is done by making sure * the linker maps this in the .text segment right after head.S, * and making head.S ensure the proper alignment. * * The things we do for performance.. */union thread_union init_thread_union __init_task_data =    { INIT_THREAD_INFO(init_task) };thread_union定义如下:union thread_union {    struct thread_info thread_info;    unsigned long stack[THREAD_SIZE/sizeof(long)];};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

其中定义了THREAD_SIZE(8KB)的静态栈空间,__mmap_switched将sp设置在了内核第一进程的栈顶部,栈向下生长,因此接下来start_kernel就运行在该内核栈。
直到rest_init中调用kernel_thread创建进程kernel_init。kernel_init的pid = 1.

__mmap_switched最后跳转到start_kernel开始进入C函数运行环境,这时整个kernel image已经运行在虚拟地址之上,运行地址 链接地址保持了一致,内核运行进入了新时代!
arm-linux启动到start_kernel的汇编运行过程就分析到这。

这段时间做PPC处理器的内核移植,对PPC的启动过程也学习了一番,有时间也将PPC-linux启动到start_kernel的过程加以对比分析!






阅读全文
0 0
原创粉丝点击