IT民工
分类: 嵌入式
2009-09-03 13:15:40
mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode //设置为SVC模式,允许中断和快速中断
//此处设定系统的工作状态,arm有7种状态
//每种状态有自己的堆栈
msr cpsr_c, r0 @ and all irqs diabled
bl __lookup_processor_type
//定义处理器相关信息,如value, mask, mmuflags,
//放在proc.info段中
//__lookup_processor_type 取得这些信息,在下面
//__lookup_architecture_type 中用
这一段是查询处理器的种类,大家知道arm有arm7, arm9等类型,如何区分呢?
在arm协处理器中有一个只读寄存器,存放处理器相关信息。__lookup_processor_type将返回如下的结构:
__arm920_proc_info:
.long 0x41009200 //CPU id
.long 0xff00fff0 //cpu mask
.long 0x00000c1e @ mmuflags
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_26BIT
.long cpu_arm920_info
.long arm920_processor_functions
第一项是CPU id,将与协处理器中读出的id作比较,其余的都是与处理器相关的
信息,到下面初始化的过程中自然会用到。。
查询到了处理器类型和系统的内存映像后就要进入初始化过程中比较关键的一步了,开始设置mmu,但首先要设置一个临时的内核页表,映射4m的内存,这在初始化过程中是足够了:
//r5=0800 0000 ram起始地址 r6=0020 0000 io地址,r7=f020 0000 虚io
teq r7, #0 @ invalid architecture?
moveq r0, #'a' @ yes, error 'a'
beq __error
bl __create_page_tables
其中__create_page_tables为:
__create_page_tables:
pgtbl r4
//r4=0800 4000 临时页表的起始地址
//r5=0800 0000, ram的起始地址
//r6=0020 0000, i/o寄存器空间的起始地址
//r7=0000 3c08
//r8=0000 0c1e
//the page table in 0800 4000 is just temp base page, when init_task's sweaper_page_dir ready,
// the temp page will be useless
// the high 12 bit of virtual address is base table index, so we need 4kx4 = 16k temp base page,
mov r0, r4
mov r3, #0
add r2, r0, #0x4000 @ 16k of page table
1: str r3, [r0], #4 @ Clear page table
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r2
bne 1b
/*
* Create identity mapping for first MB of kernel.
* This is marked cacheable and bufferable.
*
* The identity mapping will be removed by
*/
// 由于linux编译的地址是0xC0008000,load的地址是0x08008000,我们需要将虚地址0xC0008000映射到0800800一段
//同时,由于部分代码也要直接访问0x08008000,所以0x08008000对应的表项也要填充
// 页表中的表象为section,AP=11表示任何模式下可访问,domain为0。
add r3, r8, r5 @ mmuflags + start of RAM
//r3=0800 0c1e
add r0, r4, r5, lsr #18
//r0=0800 4200
str r3, [r0] @ identity mapping
//*0800 4200 = 0800 0c1e 0x200表象 对应的是0800 0000 的1m
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary.
*/
//下面是映射4M
add r0, r4, #(TEXTADDR & 0xfff00000) >> 18 @ start of kernel
//r0 = r4+ 0x3000 = 0800 4000 + 3000 = 0800 7000
str r3, [r0], #4 @ PAGE_OFFSET + 0MB
//*0800 7004 = 0800 0c1e
add r3, r3, #1 << 20
//r3=0810 0c1e
str r3, [r0], #4 @ PAGE_OFFSET + 1MB
//*0800 7008 = 0810 0c1e
add r3, r3, #1 << 20
str r3, [r0], #4
//*0800 700c = 0820 0c1e @ PAGE_OFFSET + 2MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ PAGE_OFFSET + 3MB
//*0800 7010 = 0830 0c1e
bic r8, r8, #0x0c @ turn off cacheable
//r8=0000 0c12 @ and bufferable bits
mov pc, lr //子程序返回。
下一回就要开始打开mmu的操作了
上回书讲到已经设置好了内核的页表,然后要跳转到__arm920_setup,
这个函数在arch/arm/mm/proc-arm929.s
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4@ drain write buffer on v4
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
mcr p15, 0, r4, c2, c0 @ load page table pointer
mov r0, #0x1f @ Domains 0, 1 = client
mcr p15, 0, r0, c3, c0 @ load domain access register
mrc p15, 0, r0, c1, c0 @ get control register v4
/*
* Clear out 'unwanted' bits (then put them in if we need them)
*/
@ VI ZFRS BLDP WCAM
bic r0, r0, #0x0e00
bic r0, r0, #0x0002
bic r0, r0, #0x000c
bic r0, r0, #0x1000 @ ...0 000. .... 000.
/*
* Turn on what we want
*/
orr r0, r0, #0x0031
orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1
#ifdef CONFIG_CPU_ARM920_D_CACHE_ON
orr r0, r0, #0x0004 @ .... .... .... .1..
#endif
#ifdef CONFIG_CPU_ARM920_I_CACHE_ON
orr r0, r0, #0x1000 @ ...1 .... .... ....
#endif
mov pc, lr
这一段首先关闭i,d cache,清除write buffer ,然后设置页目录地址,设置
domain的保护,在上节中,注意到页目录项的domain都是0,domain寄存器中
的domain 0 对应的是0b11,表示访问模式为manager,不受限制。
接下来设置控制寄存器,打开d,i cache和mmu
注意arm的d cache必须和mmu一起打开,而i cache可以单独打开
其实,cache和mmu的关系实在是紧密,每一个页表项都有标志标示是否是
cacheable的,可以说本来就是设计一起使用的
最后,自函数返回后,有一句
mcr p15, 0, r0, c1, c0
使设置生效。
上回我们讲到arm靠初始化完成了,打开了cache,
到此为止,汇编部分的初始化代码就差不多了,最后还有几件事情做:
1。初始化BSS段,全部清零,BSS是全局变量区域。
2。保存与系统相关的信息:如
.long SYMBOL_NAME(compat)
.long SYMBOL_NAME(__bss_start)
.long SYMBOL_NAME(_end)
.long SYMBOL_NAME(processor_id)
.long SYMBOL_NAME(__machine_arch_type)
.long SYMBOL_NAME(cr_alignment)
.long SYMBOL_NAME(init_task_union)+8192
不用讲,大家一看就明白意思
3。重新设置堆栈指针,指向init_task的堆栈。init_task是系统的第一个任务,init_task的堆栈在task structure的后8K,我们后面会看到。
4。最后就要跳到C代码的start_kernel。
b SYMBOL_NAME(start_kernel)
现在让我们来回忆一下目前的系统状态:
临时页表已经建立,在0X08004000处,映射了4M,虚地址0XC000000被映射到0X08000000.
CACHE,MMU都已经打开。
堆栈用的是任务init_task的堆栈。
如果以为到了c代码可以松一口气的话,就大错特措了,linux的c也不比汇编好懂多少,相反到掩盖了汇编的一些和机器相关的部分,有时候更难懂。其实作为编写操作系统的c代码,只不过是汇编的另一种写法,和机器代码的联系是很紧密的。
start_kernel在 /linux/init/main.c中定义:
asmlinkage void __init start_kernel(void)
{
char * command_line;
unsigned long mempages;
extern char saved_command_line[];
lock_kernel();
printk(linux_banner);
setup_arch(&command_line); //arm/kernel/setup.c
printk("Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
trap_init(); // arm/kernle/traps.c install
。。。。。。。。。
start_kernel中的函数个个都是重量级的,首先用printk(linux_banner);打出
系统版本号,这里面就大有文章,系统才刚开张,你让他打印到哪里去呢?
先 给大家交个底,以后到console的部分自然清楚,printk和printf不同,他首先输出到系统的一个缓冲区内,大约4k,如果登记了 console,则调用console->wirte函数输出,否则就一直在buffer里呆着。所以,用printk输出的信息,如果超出了 4k,会冲掉前面的。在系统引导起来后,用dmesg看的也就是这个buffer中的东东。
下面就是一个重量级的函数:
setup_arch(&command_line); //arm/kernel/setup.c
完成内存映像的初始化,其中command_line是从bootloader中传下来的。
void __init setup_arch(char **cmdline_p)
{
struct param_struct *params = NULL;
struct machine_desc *mdesc; //arch structure, for your ads, defined in include/arm-asm/mach/arch.h very long
struct meminfo meminfo;
char *from = default_command_line;
memset(&meminfo, 0, sizeof(meminfo));
首先把meminfo清零,有个背景介绍一下,从linux 2.4的内核开始,支持内存的节点(node),也就是可支持不连续的物理内存区域。这一点在嵌入式系统中很有用,例如对于SDRAM和FALSH,性质不同,可作为不同的内存节点。
meminfo结构定义如下:
/******************************************************/
#define NR_BANKS 4
//define the systen mem region, not consistent
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/******************************************************/