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
:
400000: 48 00 00 05 bl 400004
00400004 :
400004: 7c 68 02 a6 mflr r3
400008: 48 00 15 99 bl 4015a0
40000c: 48 00 16 25 bl 401630
400010: 38 63 ff fc addi r3,r3,-4
400014: 48 00 00 04 b 400018
00400018 :
在执行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
c0000028: 48 00 33 9d bl c00033c4
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 :
c0005ad4: 7c 08 02 a6 mflr r0
c0005ad8: 48 00 00 05 bl c0005adc
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 :
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
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
c00030dc: 4b ff cf 84 b c0000060
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 :
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 okir@monad.swb.de).
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/