【嵌入式Linux学习七步曲之第四篇 Linux内核移植】PPC Linux启动流程分析

 PPC Linux启动流程分析
Sailor_forever  sailing_9806#163.com 转载请注明
http://blog.csdn.net/sailor_8318/archive/2009/11/22/4853319.aspx


【摘要】本文分析了MPC8270在Linux2.6.19下的启动流程。介绍了压缩内核的链接脚本、映像生成的过程、压缩内核如何重定位解压缩及非压缩内核在MMU开启前后的启动流程。对于分析内核启动过程中的疑难杂症有重要参考意义。

【关键字】MPC8270  Linux  MMU  PIC                         

目录

1    压缩格式内核的相关代码    1
2    链接脚本    2
3    压缩内核的编译过程    3
4    压缩内核的启动    4
4.1    判断是否需要重定位    4
4.2    重定位    5
4.3    内核解压缩及加载    8
5    非压缩内核的启动    10
5.1    MMU开启之前    10
5.2    MMU开启之后    17


MPC8270的CPU是603e(G2e),和MPC8260同属于603e系列,内核中对MPC8260的支持比较丰富,在内核移植的时候可以参考。为了便于分析启动过程的一些细节,可以将elf格式的内核反汇编得到汇编代码。
命令如下:
ppc-linux-objdump -d zImage.elf > zImage.S
ppc-linux-objdump -d vmlinux > uImage.S

1    压缩格式内核的相关代码
压缩格式的内核镜像解压缩部分要利用/arch/ppc/boot/目录下面的代码,由Makefile可知
# Linker args.  This specifies where the image will be run at.
LD_ARGS                    := -T $(srctree)/$(boot)/ld.script /
                   -Ttext $(CONFIG_BOOT_LOAD) -Bstatic
OBJCOPY_ARGS            := -O elf32-powerpc

# head.o and relocate.o must be at the start.
boot-y                := head.o relocate.o $(extra.o-y) $(misc-y)
boot-$(CONFIG_REDWOOD_5)    += embed_config.o

采用的链接脚本/arch/ppc/boot/ld.script,链接地址是CONFIG_BOOT_LOAD,text段最开始的代码为head.o

2    链接脚本
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  . = + SIZEOF_HEADERS;
  .interp : { *(.interp) }
。。。。。。。。。
  .text      :
  {
    *(.text)
    *(.fixup)
    __relocate_start = .;
    *(.relocate_code)
    __relocate_end = .;
  }
  _etext = .;
  PROVIDE (etext = .);

  /* Read-write section, merged into data segment: */
  . = ALIGN(4096);
  .data    :
  {
    *(.data)
    *(.data1)
。。。。。。
    *(.got1)
    __image_begin = .;
    *(.image)
    __image_end = .;
    . = ALIGN(4096);
    __ramdisk_begin = .;
    *(.ramdisk)
    __ramdisk_end = .;
    . = ALIGN(4096);
    CONSTRUCTORS
  }
  _edata  =  .;
  PROVIDE (edata = .);

  . = ALIGN(4096);
  __bss_start = .;
  .bss       :
  {
   *(.sbss) *(.scommon)
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  _end = . ;
  PROVIDE (end = .);
}
映像主要分成了三个段:.text, .data, .bss,并设置了相关段的起始地址,便于进行代码重定位及内核解压缩

3    压缩内核的编译过程
make zImage
。。。
  LD      vmlinux
  SYSMAP  System.map
  SYSMAP  .tmp_System.map
  OBJCOPY arch/ppc/boot/images/vmlinux.bin
  GZIP    arch/ppc/boot/images/vmlinux.gz
  AS      arch/ppc/boot/simple/head.o
  AS      arch/ppc/boot/simple/relocate.o
  CC      arch/ppc/boot/simple/misc-embedded.o
  CC      arch/ppc/boot/simple/embed_config.o
  CC      arch/ppc/boot/simple/m8260_tty.o
ppc_82xx-objcopy -O elf32-powerpc /
                --add-section=.image=arch/ppc/boot/images/vmlinux.gz /
                --set-section-flags=.image=contents,alloc,load,readonly,data /
                arch/ppc/boot/simple/dummy.o arch/ppc/boot/simple/image.o
ppc_82xx-ld -m elf32ppc -T /home/sailing/linux-2.6.19/arch/ppc/boot/ld.script -Ttext 0x00400000 -Bstatic -o arch/ppc/boot/simple/zvmlinux arch/ppc/boot/simple/head.o arch/ppc/boot/simple/relocate.o arch/ppc/boot/simple/misc-embedded.o arch/ppc/boot/simple/embed_config.o arch/ppc/boot/simple/m8260_tty.o arch/ppc/boot/simple/image.o arch/ppc/boot/common/lib.a arch/ppc/boot/lib/lib.a
ppc_82xx-objcopy -O elf32-powerpc arch/ppc/boot/simple/zvmlinux arch/ppc/boot/simple/zvmlinux -R .comment -R .stab /
                -R .stabstr -R .ramdisk
cp -f arch/ppc/boot/simple/zvmlinux arch/ppc/boot/images/zImage.elf
rm -f arch/ppc/boot/simple/zvmlinux
从上面映像的编译过程可知,压缩格式的印象编译主要分成以下几个部分:
1)、以0xc0000000为虚拟地址的非压缩内核自身的编译,最终加载的地方是物理地址0;
对非压缩内核进行gzip压缩;
2)、将压缩过的内核印象转换成arch/ppc/boot/simple/dummy.o中的.image数据段;
3)、解压缩代码的编译,链接地址是0x00400000,将压缩内核插入到压缩格式内核印象数据段的.image中,并设置起始标识__image_begin和结束标识__image_end

4    压缩内核的启动
压缩内核的启动总体流程如下:
Begin at some arbitrary location in RAM
Move the boot code to the link address (8M)
Setup C stack
Initialize UART if neccessary
Decompress the kernel to 0x0
Jump to the kernel entry

4.1    判断是否需要重定位
Head.S中声明start符号,程序就从这里开始运行。因为内核可能运行在非链接指定地址上,而链接期又对地址做了假设,访问全局变量使用的是绝对地址,因为需要确保程序运行在链接地址上。这样就要求内核知道自己运行的地址,以便决定是否重定位。取得的方法是非常巧妙的:
00400000 <start>:
  400000:    48 00 00 05     bl      400004 <start_>

00400004 <start_>:
  400004:    7c 68 02 a6     mflr    r3
  400008:    48 00 15 99     bl      4015a0 <disable_6xx_mmu>
  40000c:    48 00 16 25     bl      401630 <disable_6xx_l1cache>
  400010:    38 63 ff fc     addi    r3,r3,-4
  400014:    48 00 00 04     b       400018 <relocate>

00400018 <relocate>:
在执行bl命令的时候,当前地址加4会被保存在lr寄存器中。mflr    r3命令会将lr寄存器中的值保存在r3中。在EABI规范中定义r3是第一参数,addi    r3,r3,-4一句得到符号〈start〉的实际地址。从ld.script可以得知符号是Text段的第一句,Text段又位于内核镜像之首,因而r3中现在就保存了内核镜像的加载首地址。BL和B等跳转指令都是相对PC寻址的,是位置无关指令。

在保存启动地址在r3之后,跳到relocate。
Relocate位于arch/ppc/boot/simple/relocate.S中。
/* We get called from the early initialization code.
     * Register 3 has the address where we were loaded,
     * Register 4 contains any residual data passed from the
     * boot rom.
     */
    .globl    relocate
relocate:
    /* Save r3, r4 for later.
     * The r8/r11 are legacy registers so I don't have to
     * rewrite the code below :-).
     */
    mr    r8, r3
    mr    r11, r4
    /* compute the size of the whole image in words. */
    GETSYM(r4,start)
    GETSYM(r5,end)
/*
     * Check if we need to relocate ourselves to the link addr or were
     * we loaded there to begin with.
     */
    cmpw    cr0,r3,r4
    beq    start_ldr    /* If 0, we don't need to relocate */
。。。。。。。
    .previous
start_ldr:

函数relocate的流程如下:
1)、r3装载地址,保留在r8中。
2)、r4 boot传来的地址,保留在r11中。
3)、r7 = (r5 - r4 +3)/4 计算出压缩映像大小的Word数。R5和R4的值是在ld.script中确定的。R7保留待用。
4)、如果r3和r4相等,则无需对映像进行重定位,直接跳转到start_ldr;否则需要将映像重新加载到链接地址。

4.2    重定位
    /* Move this code somewhere safe.  This is max(load + size, end)
     * r8 == load address
     */
    GETSYM(r4, start)
    GETSYM(r5, end)

    sub    r6,r5,r4
    add    r6,r8,r6    /* r6 == phys(load + size) */

    cmpw    r5,r6
    bgt    1f
    b    2f
1:
    mr    r6, r5
2:
    /* dest is in r6 */
    /* Ensure alignment --- this code is precautionary */
    addi    r6,r6,4
    li    r5,0x0003
    andc    r6,r6,r5

    /* Find physical address and size of do_relocate */
    GETSYM(r5, __relocate_start)
    GETSYM(r4, __relocate_end)
    GETSYM(r3, start)

    /* Size to copy */
    sub    r4,r4,r5
    srwi    r4,r4,2

    /* Src addr to copy (= __relocate_start - start + where_loaded) */
    sub    r3,r5,r3
    add    r5,r8,r3

    /* Save dest */
    mr    r3, r6

    /* Do the copy */
    mtctr    r4
3:    lwz    r4,0(r5)
    stw    r4,0(r3)
    addi    r3,r3,4
    addi    r5,r5,4
    bdnz    3b

    GETSYM(r4, __relocate_start)
    GETSYM(r5, do_relocate)

    sub    r4,r5,r4    /* Get entry point for do_relocate in */
    add    r6,r6,r4    /* relocated section */

    /* This will return to the relocated do_relocate */
    mtlr    r6
    b    flush_instruction_cache

    .section ".relocate_code","xa"
do_relocate:
    /* We have 2 cases --- start < load, or start > load
     * This determines whether we copy from the end, or the start.
     * Its easier to have 2 loops than to have paramaterised
     * loops.  Sigh.
     */
。。。。。
do_relocate_out:
    GETSYM(r3,start_ldr)
    mtlr    r3        /* Easiest way to do an absolute jump */
b    flush_instruction_cache

重定位流程如下:
1)、确定加载的映像的末地址和链接地址的末地址大小,取最大值;
2)、将专门重定位的段.relocate_code拷贝到上述最大地址处,因此压缩内核不能从flash直接启动;.relocate_code是位置无关代码,然后跳转到.relocate_code的do_relocate处。这样运行do_relocate的时候,取指令和拷贝数据不会访问同一个内存地址;
3)、比较当前映像的加载地址和链接地址的大小,决定拷贝方式,避免覆盖;
4)、拷贝完毕后,刷新指令cache,绝对跳转到start_ldr的链接地址,进行内核解压

绝对跳转的实现非常巧妙,同时避免了指令cache的影响。
mtlr    r3模拟了一个函数调用时自动保存下一条指令地址至LR的操作,将待跳转的指令地址赋值给lr,再进行绝对跳转至flush_instruction_cache,这样flush_instruction_cache返回时的地址就不是跳转语句的下一条指令来。刷新cache,blr跳转到lr的地址。
    mtlr    r3        /* Easiest way to do an absolute jump */
b    flush_instruction_cache
    .section ".relocate_code","xa"
/*
 * Flush and enable instruction cache
 * First, flush the data cache in case it was enabled and may be
 * holding instructions for copy back.
 */
        .globl flush_instruction_cache
flush_instruction_cache:        
    mflr    r6
    bl    flush_data_cache
。。。
    /* Enable, invalidate and then disable the L1 icache/dcache. */
    li    r3,0
    ori    r3,r3,(HID0_ICE|HID0_DCE|HID0_ICFI|HID0_DCI)
    mfspr    r4,SPRN_HID0
    or    r5,r4,r3
    isync
    mtspr    SPRN_HID0,r5
    sync
    isync
    ori    r5,r4,HID0_ICE    /* Enable cache */
    mtspr    SPRN_HID0,r5
    sync
    isync
#endif
    mtlr    r6
    blr

#define NUM_CACHE_LINES 128*8
#define cache_flush_buffer 0x1000

/*
 * Flush data cache
 * Do this by just reading lots of stuff into the cache.
 */
        .globl flush_data_cache
flush_data_cache:       
    lis    r3,cache_flush_buffer@h
    ori    r3,r3,cache_flush_buffer@l
    li    r4,NUM_CACHE_LINES
    mtctr    r4
00:    lwz    r4,0(r3)
    addi    r3,r3,L1_CACHE_BYTES    /* Next line, please */
    bdnz    00b
10:    blr
    .previous

4.3    内核解压缩及加载
start_ldr 位于arch/ppc/boot/simple/relocate.S
主要功能是清除BSS段、设置堆栈为调用C函数作准备。
  _edata  =  .;
  PROVIDE (edata = .);
  . = ALIGN(4096);
  __bss_start = .;
  .bss       :
  {
   *(.sbss) *(.scommon)
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  _end = . ;
    .previous
start_ldr:
/* Clear all of BSS and set up stack for C calls */
    lis    r3,__bss_start@h
    ori    r3,r3,__bss_start@l
    lis    r4,end@h
    ori    r4,r4,end@l
    subi    r3,r3,4
    subi    r4,r4,4
    li    r0,0
50:    stwu    r0,4(r3)
    cmpw    cr0,r3,r4
    blt    50b
90:    mr    r9,r1        /* Save old stack pointer (in case it matters) */
    lis    r1,.stack@h
    ori    r1,r1,.stack@l
    addi    r1,r1,4096*2
    subi    r1,r1,256
    li    r2,0x000F    /* Mask pointer to 16-byte boundary */
    andc    r1,r1,r2
    /*
     * Exec kernel loader
     */
    mr    r3,r8        /* Load point */
    mr    r4,r7        /* Program length */
    mr    r5,r6        /* Checksum */
    mr    r6,r11        /* Residual data */
    mr    r7,r25        /* Validated OFW interface */
    bl    load_kernel

此后跳到load_kernel
unsigned long load_kernel(…)位于 /arch/ppc/boot/simple/Misc-embedded.c
unsigned long
load_kernel(unsigned long load_addr, int num_words, unsigned long cksum, bd_t *bp)
此处bd_t的数据结构需要和uboot中的一种,因为要注意Linux内核和uboot版本的对应关系

load_kernel函数流程:
1)、板子特定参数初始化,这个需要移植;
2)、视需要初始化串口,具体哪个串口需要根据板子移植;
3)、将BootLoader传来的参数重定位,以便内核启动初期能够访问;
4)、调用gunzip函数解压缩内核映像。解压于0。
5)、调用flush_instruction_cache,位于/arch/ppc/boot/common/util.S。
6)、在显示"Unpressing Linux…done"之后初始化RamDisk。
7)、命令行命令解析。
输出信息:"Now booting the kernel/n"并向start_ldr返回Bootloader传来的hold_residual参数。因为前面已经将内核解压缩到0x00000000地址,此时跳转到0。之所以不用bl或b命令是因为这些都为相对跳转,跳转的范围有限,而blr是直接对PC进行赋值,是绝对跳转,可以寻址4G的空间。到此,Load_kernel执行完毕,最后跳转到vmlinux中。

5    非压缩内核的启动
5.1    MMU开启之前
vmLinux从arch/ppc/kernel/head.S开始执行。
1、 保存参数,有效参数存在R3中,是从BootLoader中传来的结构体,保存一些基本信息。
c0000000 <_start>:
c0000000:       60 00 00 00     nop
c0000004:       60 00 00 00     nop
c0000008:       60 00 00 00     nop
最开始这三个nop是为了解决潜伏期问题。
c000000c <__start>:
c000000c:       7c 7f 1b 78     mr      r31,r3
c0000010:       7c 9e 23 78     mr      r30,r4
c0000014:       7c bd 2b 78     mr      r29,r5
c0000018:       7c dc 33 78     mr      r28,r6
c000001c:       7c fb 3b 78     mr      r27,r7
将从BootLoader或者是从zImage传来的参数保存起来。对于zImage,只有r3有效
之后调用两个函数:
c0000020:       3b 00 00 00     li      r24,0
c0000024:       48 28 43 85     bl      c02843a8 <early_init>
c0000028:       48 00 33 9d     bl      c00033c4 <mmu_off>

2、 调用early_init()位于 arch/ppc/kernel/setup.c,
其辨别CPU类型,并调用一些底层初始化函数
__init unsigned long early_init(int r3, int r4, int r5)
{
  unsigned long phys;
 unsigned long offset = reloc_offset();
/////////
reloc_offset()位于arch/ppc/kernel/misc.S
_GLOBAL(reloc_offset)
    mflr    r0
    bl    1f
1:    mflr    r3
    lis    r4,1b@ha
    addi    r4,r4,1b@l
    subf    r3,r4,r3
    mtlr    r0
    blr
c0005ad4 <reloc_offset>:
c0005ad4:       7c 08 02 a6     mflr    r0
c0005ad8:       48 00 00 05     bl      c0005adc <reloc_offset+0x8>
c0005adc:       7c 68 02 a6     mflr    r3
c0005ae0:       3c 80 c0 00     lis     r4,-16384
c0005ae4:       38 84 5a dc     addi    r4,r4,23260    
c0005ae8:       7c 64 18 50     subf    r3,r4,r3
c0005aec:       7c 08 03 a6     mtlr    r0
c0005af0:       4e 80 00 20     blr
a) 获取偏移,方法:由编译链接期获得的地址减去执行的实际地址。
i. 链接地址:1:
ii. 实际地址:LR中保存的地址,在跳转指令执行的同时保存。跳转指令的下一条指令即为1:的运行地址
b) 偏移量是用来在非链接地址上运行,访问全局变量的时候需要加上的地址。因为内核的链接地址是虚拟地址,而启动阶段前期未打开MMU之前使用的是物理地址,二者不等
/////////////
/ * phys即为当前内核加载的物理地址,对于压缩内核自动解压至0地址,非压缩内核取决于uboot指定的-e地址。此方法根据程序运行的情况动态计算加载地址,可以适应各种情况 */
  phys = offset + KERNELBASE;
 / * First zero the BSS -- use memset, some arches don't have caches on yet */
 memset_io(PTRRELOC(&__bss_start), 0, _end - __bss_start);
#define PTRRELOC(x)    ((typeof(x)) add_reloc_offset((unsigned long)(x)))
 / *
  * Identify the CPU type and fix up code sections
  * that depend on which cpu we have.
  */
 identify_cpu(offset, 0);
//////////////////////
offset是访存的时候需要做的偏移。identify_cpu位于/arch/powerpc/kernel/cputable.c
struct cpu_spec *identify_cpu(unsigned long offset)
{
    struct cpu_spec *s = cpu_specs;
    struct cpu_spec **cur = &cur_cpu_spec;
    unsigned int pvr = mfspr(SPRN_PVR);
    int i;

    s = PTRRELOC(s);
    cur = PTRRELOC(cur);

    if (*cur != NULL)
        return PTRRELOC(*cur);

    for (i = 0; i < ARRAY_SIZE(cpu_specs); i++,s++)
        if ((pvr & s->pvr_mask) == s->pvr_value) {
            *cur = cpu_specs + i;
            return s;
        }
    BUG();
    return NULL;
}
cpu_specs是保存CPU信息的结构体。G2_LE是603e的一个分支版本,多数时候和603e是相同的。只是在细节上有些差异。Mpc8270和8280属于G2_LE内核。
linux-2.6.19/arch/ppc/kernel/cputable.c 中cpu_specs结构体的内容如下:
struct cpu_spec cpu_specs[] = {
.
    {    /* 82xx (8240, 8245, 8260 are all 603e cores) */
        .pvr_mask        = 0x7fff0000,
        .pvr_value        = 0x00810000,
        .cpu_name        = "82xx",
        .cpu_features        = CPU_FTR_COMMON |
            CPU_FTR_SPLIT_ID_CACHE | CPU_FTR_MAYBE_CAN_DOZE |
            CPU_FTR_USE_TB,
        .cpu_user_features    = COMMON_PPC,
        .icache_bsize        = 32,
        .dcache_bsize        = 32,
        .cpu_setup        = __setup_cpu_603
    },
    {    /* All G2_LE (603e core, plus some) have the same pvr */
        .pvr_mask        = 0x7fff0000,
        .pvr_value        = 0x00820000,
        .cpu_name        = "G2_LE",
        .cpu_features        = CPU_FTR_SPLIT_ID_CACHE |
            CPU_FTR_MAYBE_CAN_DOZE | CPU_FTR_USE_TB |
            CPU_FTR_MAYBE_CAN_NAP | CPU_FTR_HAS_HIGH_BATS,
        .cpu_user_features    = COMMON_PPC,
        .icache_bsize        = 32,
        .dcache_bsize        = 32,
        .cpu_setup        = __setup_cpu_603
    }, .
 .
  {/* default match, we assume split I/D cache & TB (non-601)... */
 。。。。。。。。
  .dcache_bsize  = 32,
  .cpu_setup  = __setup_cpu_generic
  }
};
/////////////////
获得CPU类型,然后根据CPU类型填充段。
    do_feature_fixups(spec->cpu_features,
              PTRRELOC(&__start___ftr_fixup),
              PTRRELOC(&__stop___ftr_fixup));
///////////////////
do_feature_fixups位于/arch/ppc/kernel/cputable.c中。其功能是依据CPU的功能将不需要的启动代码写成NOP。

3、 调用mmu_off 位于arch/ppc/kernel/head.S
函数流程:
a) 关闭MSR中的DR,IR位。
b) 禁止MMU
4、 调用clear_bats 位于arch/ppc/kernel/head.S清除BAT_Entry。
5、 调用flush_tlbs 位于arch/ppc/kernel/head.S 清除TLB。
6、 调用initial_bats 位于 arch/ppc/kernel/head.S
/*
 * Use the first pair of BAT registers to map the 1st 16MB
 * of RAM to KERNELBASE.  From this point on we can't safely
 * call OF any more.
 */
initial_bats:
    lis    r11,KERNELBASE@h
    mfspr    r9,SPRN_PVR
    rlwinm    r9,r9,16,16,31        /* r9 = 1 for 601, 4 for 604 */
    cmpwi    0,r9,1
    bne    4f
    ori    r11,r11,4        /* set up BAT registers for 601 */
    li    r8,0x7f            /* valid, block length = 8MB */
    oris    r9,r11,0x800000@h    /* set up BAT reg for 2nd 8M */
    oris    r10,r8,0x800000@h    /* set up BAT reg for 2nd 8M */
    mtspr    SPRN_IBAT0U,r11        /* N.B. 601 has valid bit in */
    mtspr    SPRN_IBAT0L,r8        /* lower BAT register */
    mtspr    SPRN_IBAT1U,r9
    mtspr    SPRN_IBAT1L,r10
    isync
    blr
4:    tophys(r8,r11)
。。。。
7、 调用reloc_offset 位于/arch/ppc/kernel/misc.S。通过一次跳转得到执行地址,保存在LR中,链接时地址通过跳转符号得到。此后在重定位内核之前,变量访问的地址要加上R3。
8、 调用call_setup_cpu位于/arch/ppc/kernel/misc.S。这里面引用了一个结构体cpu_spec,其定义在include/asm-ppc/cputable.h之中。这段函数是调用注册到cur_cpu_spec中的cpu初始化函数。
/*
 * call_setup_cpu - call the setup_cpu function for this cpu
 * r3 = data offset, r24 = cpu number
 *
 * Setup function is called with:
 *   r3 = data offset
 *   r4 = ptr to CPU spec (relocated)
 */
_GLOBAL(call_setup_cpu)
 addis r4,r3,cur_cpu_spec@ha
 addi r4,r4,cur_cpu_spec@l
 lwz r4,0(r4)
 add r4,r4,r3
 lwz r5,CPU_SPEC_SETUP(r4)
 cmpi 0,r5,0
 add r5,r5,r3
 beqlr
 mtctr r5
 bctr
这段代码相当于:
if(cur_cpu_spec-> cpu_setup != NULL )
  cur_cpu_spec-> cpu_setup ();

_GLOBAL(__setup_cpu_603)
    b    setup_common_caches

此处实际调用了下面的函数:
/* Enable caches for 603's, 604, 750 & 7400 */
setup_common_caches:
 mfspr r11,SPRN_HID0    // move from Hardware Implementation Register 0 to r11
 andi. r0,r11,HID0_DCE    // r0 = r11 & Data Cache Enable Bit
 ori r11,r11,HID0_ICE|HID0_DCE  
 ori r8,r11,HID0_ICFI
 bne 1f      /* don't invalidate the D-cache */
 ori r8,r8,HID0_DCI    /* unless it wasn't enabled */
1: sync
 mtspr SPRN_HID0,r8    /* enable and invalidate caches */
 sync
 mtspr SPRN_HID0,r11    /* enable caches */
 sync
 isync
 blr

9、 为防止R3被修改过,再次调用reloc_offset。
10、 调用init_idle_6xx,清除NAP标志。

11、 如果内核没有运行在0地址上,则调用relocate_kernel位于/arch/ppc/kernel/head.S,将前0x4000字节复制到0地址,然后绝对跳转至重定位之后的"4:    mr    r5,r25"地址上,之后再拷贝剩余的内核,然后相对跳转至turn_on_mmu。
c00030ac <relocate_kernel>:
c00030ac:    3d 3a c0 27     addis   r9,r26,-16345
c00030b0:    83 29 37 44     lwz     r25,14148(r9)
c00030b4:    3f 39 40 00     addis   r25,r25,16384
c00030b8:    38 60 00 00     li      r3,0
c00030bc:    38 c0 00 00     li      r6,0
c00030c0:    38 a0 40 00     li      r5,16384
c00030c4:    48 00 00 1d     bl      c00030e0 <copy_and_flush>
c00030c8:    38 03 30 d4     addi    r0,r3,12500
c00030cc:    7c 09 03 a6     mtctr   r0
c00030d0:    4e 80 04 20     bctr
c00030d4:    7f 25 cb 78     mr      r5,r25
c00030d8:    48 00 00 09     bl      c00030e0 <copy_and_flush>
c00030dc:    4b ff cf 84     b       c0000060 <turn_on_mmu>

turn_on_mmu:
    mfmsr    r0
    ori    r0,r0,MSR_DR|MSR_IR
    mtspr    SPRN_SRR1,r0
    lis    r0,start_here@h
    ori    r0,r0,start_here@l
    mtspr    SPRN_SRR0,r0
    SYNC
    RFI                /* enables MMU */
c0000060 <turn_on_mmu>:
c0000060:    7c 00 00 a6     mfmsr   r0
c0000064:    60 00 00 30     ori     r0,r0,48
c0000068:    7c 1b 03 a6     mtsrr1  r0
c000006c:    3c 00 c0 00     lis     r0,-16384
c0000070:    60 00 32 58     ori     r0,r0,12888
c0000074:    7c 1a 03 a6     mtsrr0  r0
c0000078:    4c 00 00 64     rfi
RFI是中断返回指令,其自动将SRR1更新为MSR,并在新的MSR控制下将SRR0更新为PC指针,实现绝对跳转,此时程序便运行在虚拟地址start_here上。在此之后,就不再有前面提到的链接时地址和运行时地址不同的问题,对变量的访问也不需要加上偏移。

12、 在打开MMU之后,跳转到start_here位于/arch/ppc/kernel/head.S,为内核启动做最后的准备工作,之后真正的内核工作就可以展开了。首要的工作是为init_task的运行做准备。先要取得Task结构体地址,将结构体的指针保存在操作系统专用的寄存器SPRG3里面

13、 调用machine_init()函数位于/arch/ppc/kernel/Setup.c。该函数主要调用了platform_init函数。这个函数是移植的时候修改比较集中的地方,集中了许多板级资源的注册,在相应板子的setup文件中。
/* Inputs:
 *   r3 - Optional pointer to a board information structure.
 *   r4 - Optional pointer to the physical starting address of the init RAM
 *        disk.
 *   r5 - Optional pointer to the physical ending address of the init RAM
 *        disk.
 *   r6 - Optional pointer to the physical starting address of any kernel
 *        command-line parameters.
 *   r7 - Optional pointer to the physical ending address of any kernel
 *        command-line parameters.
 */
void __init
platform_init(unsigned long r3, unsigned long r4, unsigned long r5,
          unsigned long r6, unsigned long r7)
{
    parse_bootinfo(find_bootinfo());

    if ( r3 )
        memcpy( (void *)__res,(void *)(r3+KERNELBASE), sizeof(bd_t) );

#ifdef CONFIG_BLK_DEV_INITRD
    /* take care of initrd if we have one */
    if ( r4 ) {
        initrd_start = r4 + KERNELBASE;
        initrd_end = r5 + KERNELBASE;
    }
#endif /* CONFIG_BLK_DEV_INITRD */
    /* take care of cmd line */
    if ( r6 ) {
        *(char *)(r7+KERNELBASE) = 0;
        strcpy(cmd_line, (char *)(r6+KERNELBASE));
    }

    ppc_md.setup_arch        = m8260_setup_arch;
    ppc_md.show_cpuinfo        = m8260_show_cpuinfo;
    ppc_md.init_IRQ            = m8260_init_IRQ;
    ppc_md.get_irq            = cpm2_get_irq;

    ppc_md.restart            = m8260_restart;
    ppc_md.power_off        = m8260_power_off;
    ppc_md.halt            = m8260_halt;

    ppc_md.set_rtc_time        = m8260_set_rtc_time;
    ppc_md.get_rtc_time        = m8260_get_rtc_time;
    ppc_md.calibrate_decr        = m8260_calibrate_decr;

    ppc_md.find_end_of_memory    = m8260_find_end_of_memory;
    ppc_md.setup_io_mappings    = m8260_map_io;

    /* Call back for board-specific settings and overrides. */
    m82xx_board_init();
}

解析启动信息,包括命令行参数,initrd等

14、调用MMU_init()函数位于/arch/ppc/mm/Init.c。该函数仅调用一次,因而位于init段内。主要功能是建立内存映射,并初始化MMU的硬件。之后关闭MMU,将内核content载入MMU,调用load_up_mmu()函数。此后就转入到内核的初始化函数start_kernel(),汇编语言的使用也告一段落。

5.2    MMU开启之后

1、setup_arch(&command_line)
Kernel/ppc/setup.c
其解析命令行参数,调用ppc_md.setup_arch();
此函数在platform_init中注册为m8260_setup_arch
m8260_setup_arch在/arch/ppc/syslib/ m8260_setup.c中
其一个重要的作用就是对IMMR进行重映射,转换为虚拟地址,此后对寄存器的访问必须基于cpm2_immr

2、console_init()
初始化控制台,从此之后打印输出便可以真正输出到串口终端上。
console_init位于drivers/tty_io.c中
    call = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }
调用所有注册的console_initcall(cpm_uart_console_init);
static struct console cpm_scc_uart_console = {
    .name        = "ttyCPM",
    .write        = cpm_uart_console_write,
    .device        = uart_console_device,
    .setup        = cpm_uart_console_setup,
    .flags        = CON_PRINTBUFFER,
    .index        = -1,
    .data        = &cpm_reg,
};

int __init cpm_uart_console_init(void)
{
    register_console(&cpm_scc_uart_console);
    return 0;
}

3、rest_init
其启用第一个内核线程init,变开始了内核的调度

4、populate_rootfs
检查文件系统是否是ramdisk,若是,则解压缩

5、do_basic_setup
其调用do_initcalls,调用所有的init修饰的函数
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
    initcall_t *call;
    int count = preempt_count();

    for (call = __initcall_start; call < __initcall_end; call++) {
        char *msg = NULL;
        char msgbuf[40];
        int result;

        if (initcall_debug) {
            printk("Calling initcall 0x%p", *call);
            print_fn_descriptor_symbol(": %s()",
                    (unsigned long) *call);
            printk("/n");
        }

        result = (*call)();
。。。。
}
此处主要是驱动模块初始化。
另外一个重要的工作就是调用mount_root,挂接文件系统

6、打开用户空间的console
sys_open((const char __user *) "/dev/console", O_RDWR, 0)

7、调用初始化脚本
    if (execute_command) {
        run_init_process(execute_command);
        printk(KERN_WARNING "Failed to execute %s.  Attempting "
                    "defaults.../n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");
execute_command为命令行参数提供的初始化脚本,否则调用/sbin/init,其将读取inittab脚本,进行相关配置,并最终启动shell

一个完整的启动log如下:
Linux version 2.6.15.5 (sailing@cnbjc0052) (gcc version 4.2.2) #6 Thu Nov 12 18:23:20 CST 2009
IN2 Systems TQM8260 PowerPC port
Built 1 zonelists
Kernel command line: console=ttyCPM1,19200 root=/dev/nfs rw nfsroot=150.236.70.120:/opt/ramdisk_dir/ ip=150.236.68.222:150.236.70.120:150.236.68.129:255.255.255.128:eric::off
PID hash table entries: 512 (order: 9, 8192 bytes)
Warning: real time clock seems stuck!
Console: colour dummy device 80x25
Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
Memory: 62512k available (1552k kernel code, 432k data, 92k init, 0k highmem)
Mount-cache hash table entries: 512
NET: Registered protocol family 16
Installing knfsd (copyright (C) 1996 [email protected]).
io scheduler noop registered
io scheduler anticipatory registered
io scheduler deadline registered
io scheduler cfq registered
i8042.c: No controller found.
Serial: CPM driver $Revision: 0.01 $
ttyCPM1 at MMIO 0xdffd1a90 (irq = 5) is a CPM UART
RAMDISK driver initialized: 16 RAM disks of 12388K size 1024 blocksize
fs_enet.c:v1.0 (Aug 8, 2005)
eth0: FCC ENET Version 0.3, 00:a0:1e:a8:7b:cb
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 4096 (order: 2, 16384 bytes)
TCP bind hash table entries: 4096 (order: 2, 16384 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP reno registered
TCP bic registered
NET: Registered protocol family 1
NET: Registered protocol family 17
IP-Config: Complete:
      device=eth0, addr=150.236.68.222, mask=255.255.255.128, gw=150.236.68.129,
     host=eric, domain=, nis-domain=(none),
     bootserver=150.236.70.120, rootserver=150.236.70.120, rootpath=
Looking up port of RPC 100003/2 on 150.236.70.120
Looking up port of RPC 100005/1 on 150.236.70.120
VFS: Mounted root (nfs filesystem).
Freeing unused kernel memory: 92k init
init started:  BusyBox v1.2.2 (2009.11.12-10:40+0000) multi-call binary

Please press Enter to activate this console.

相关参考资料
http://penguinppc.org/kernel/
http://penguinppc.org/embedded/
http://emzoo.bokee.com/4706874.html
http://www.kernel.org/pub/linux/kernel/v2.6/

你可能感兴趣的:(【嵌入式Linux学习七步曲之第四篇 Linux内核移植】PPC Linux启动流程分析)