一位大师级的人物写的,不看要懊悔的哟!!
LINUX发动进程
首要,portinglinux的时分要规划内存印象,如小弟的体系有64mSDRAM,
地址从0x08000000-0x0bffffff,32mflash,地址从0x0c000000-0x0dffffff.
规划如下:bootloader,linuxkernel,rootdisk放在flash里。
详细从0x0c000000开端的榜首个1M放bootloader,
0x0c100000开端的2m放linuxkernel,从0x0c300000开端都给rootdisk。
发动:
首要,发动后arm920T将地址0x0c000000映射到0(可通过跳线设置),
实际上从0x0c000000发动,进入咱们的bootloader,但因为flash速度慢,
所以bootloader前面有一小段程序把bootloader复制到SDRAM中的0x0AFE0100,
再从0x08000000运转bootloader,咱们叫这段小程序为flashloader,
flashloader必需要首要初始化SDRAM,否则往那放那些东东:
.equSOURCE,0x0C000100bootloader的寄存地址
.equTARGET,0x0AFE0100方针地址
.equSDCTL0,0x221000SDRAM操控器寄存器
//sizeisstoredinlocation0x0C0000FC
.global_start
_start://进口点
//;*
//;*InitSDRAM
//;*
//*
//*SDRAM
//*
LDRr1,=SDCTL0//
//SetPrechargeCommand
LDRr3,=0x92120200
//ldrr3,=0x92120251
STRr3,[r1]
//IssuePrechargeAllCommad
LDRr3,=0x8200000
LDRr2,[r3]
//SetAutoRefreshCommand
LDRr3,=0xA2120200
STRr3,[r1]
//IssueAutoRefreshCommand
LDRr3,=0x8000000
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
LDRr2,[r3]
//SetModeRegister
LDRr3,=0xB2120200
STRr3,[r1]
//IssueModeRegisterCommand
LDRr3,=0x08111800//;ModeRegistervalue
LDRr2,[r3]
//SetNormalMode
LDRr3,=0x82124200
STRr3,[r1]
//;*
//;*EndofSDRAMandSyncFlashInit*
//;*
//copycodefromFLASHtoSRAM
_CopyCodes:
ldrr0,=SOURCE
ldrr1,=TARGET
subr3,r0,#4
ldrr2,[r3]
_CopyLoop:
ldrr3,[r0]
strr3,[r1]
addr0,r0,#4
addr1,r1,#4
subr2,r2,#4
teqr2,#0
beq_EndCopy
b_CopyLoop
_EndCopy:
ldrr0,=TARGET
movpc,r0
上回书提到flashloader把bootloaderload到0x0AFE0100,然回跳了曩昔,
其实0x0AFE0100便是烧在flash0x0C000100中的真实的bootloader:
bootloader有几个文件组成,先是START.s,也是仅有的一个汇编程序,其他的都是C写成的,START.s首要初始化仓库:
_start:
ldrr1,=StackInit
ldrsp,[r1]
bmain
//此处咱们跳到了C代码的main函数,当C代码履行完后,还要调用
//下面的JumpToKernel0x跳到LINXUkernel运转
.equStackInitvalue,__end_data+0x1000//4K__end_data在衔接脚本中指定
StackInit:
.longStackInitvalue
.globalJumpToKernel
JumpToKernel:
//jumptothecopycode(gettheargumentsright)
movpc,r0
.globalJumpToKernel0x
//r0=jumpaddress
//r1-r4=argumentstouse(thesegetshifted)
JumpToKernel0x:
//jumptothecopycode(gettheargumentsright)
movr8,r0
movr0,r1
movr1,r2
movr2,r3
movr3,r4
movpc,r8
.section”.data.boot”
.section”.bss.boot”
下面让咱们看看bootloader的c代码干了些什么。main函数比较长,让咱们分段渐渐看。
intmain()
{
U32*pSource,*pDestin,count;
U8countDown,bootOption;
U32delayCount;
U32fileSize,i;
charc;
char*pCmdLine;
char*pMem;
init();//初始化FLASH操控器和CPU时钟
EUARTinit();//串口初始化
EUARTputString(“/n/nDBMX1LinuxBootloaderver0.2.0/n”);
EUARTputString(“Copyright(C)2002MotorolaLtd./n/n”);
EUARTputString((U8*)cmdLine);
EUARTputString(“/n/n”);
EUARTputString(“Pressanykeyforalternateboot-upoptions…”);
小弟的bootloader首要干这么几件事:init();初始化硬件,打印一些信息和供给一些操作选项:
0.Programbootloaderimage
1.Programkernelimage
2.Programroot-diskimage
3.DownloadkernelandbootfromRAM
4.Downloadkernelandbootwithver0.1.xbootloaderformat
5.Bootaver0.1.xkernel
6.Bootwithadifferentcommandline
也便是说,能够在bootloader里挑选从头下载kernel,rootdisk并写入flash,
下载的办法是用usb衔接,10m的rootdisk也就刷的一下。关于usb下载的评论请参看从前的贴子“为arm开发渠道添加usb下载接口“。
假如不选,直接回车,就开端把整个linux的内核复制到SDRAM中运转。
列位看官,或许有人要问,在flashloader中不是现已初始化过sdram操控器了吗?怎样init();中还要初始化呢,各位有所不知,小弟用的是syncflash,
能够直接运用sdram操控器的接口,牢记:在flash中运转的代码是不能初始化衔接flash的sdram操控器的,否则肯定死掉了。所以,当程序在flash中运转的时分,去初始化sdram,而现在在sdram中运转,可放心大胆地初始化flash了,首要是设定字宽,队伍延时,因为缺省都是最大的。
别的,假如列位看官的cpu有满足的片内ram,完全能够先把bootloader放在片内ram,干完悉数后再跳到LINUX,小弟着也是不得已而为之啊。
假如直接输入回车,进入kernel复制作业:
EUARTputString(“CopyingkernelfromFlashtoRAM…/n”);
count=0x200000;//2Mbytes
pSource=(U32*)0x0C100000;
pDestin=(U32*)0x08008000;
do
{
*(pDestin++)=*(pSource++);
count-=4;
}while(count>0);
}
EUARTputString(“Bootingkernel…/n/n”);
这一段没有什么可说的,运转完后kernel就在0x08008000了,至于为什么要
空出0x8000的一段,首要是放kelnel的一些大局数据结构,如内核页表,arm的页目录要有16k大。
咱们知道,linux内核发动的时分能够传入参数,如在PC上,假如运用LILO,
当呈现LILO:,咱们能够输入root=/dev/hda1.或mem=128M等指定文件体系的设备或内存大小,在嵌入式体系上,参数的传入是要靠bootloader完结的,
pMem=(char*)0x083FF000;//参数字符串的方针寄存地址
pCmdLine=(char*)&cmdLine;//界说的静态字符串
while((*(pMem++)=*(pCmdLine++))!=0);//复制
JumpToKernel((void*)0x8008000,0x083FF000)//跳转到内核
return(0);
JumpToKernel在前文中的start.S界说过:
JumpToKernel:
//jumptothecopycode(gettheargumentsright)
movpc,r0
.globalJumpToKernel0x
//r0=jumpaddress
//r1=argumentstouse(thesegetshifted)
因为arm-GCC的c参数调用的次序是从左到右R0开端,所以R0是KERNKEL的地址,
r1是参数字符串的地址:
到此为止,为linux引导做的准备作业就完毕了,下一回咱们就正式进入linux的代码。
好,从本节开端,咱们走过了bootloader的绵长征程,开端进入linux的内核:
说实话,linux宝典确实不可捉摸,洋人花了十几年修炼,各种内功心法层处不穷。有些当地反复推敲也领会不了其间微妙,炼不到第九重啊。。
linux的进口是一段汇编代码,用于根本的硬件设置和树立暂时页表,关于
ARMLINUX是linux/arch/arm/kernle/head-armv.S,走!
#ifdefined(CONFIG_MX1)
movr1,#MACH_TYPE_MX1
#endif
这榜首句话如同就让人看不懂,如同葵花宝典最初的八个字:欲练神功。。。。
那来的MACH_TYPE_MX1?其实,在head-armv.S
中的一项重要作业便是设置内核的暂时页表,否则mmu开起来也玩不转,可是内核怎样知道怎样映射内存呢?linux的内核将映射到虚地址0xCxxxxxxx处,但他怎样知道把哪一片ram映射曩昔呢?
因为不通的体系有不通的内存印象,所以,LINUX约好,内核代码开端的时分,
R1放的是体系方针渠道的代号,关于一些常见的,规范的渠道,内核现已供给了支撑,只需在编译的时分选中就行了,例如对X86渠道,内核是从物理地址1M开端映射的。假如老兄是自己攒的渠道,只好费事你自己写了。
小弟拿人金钱,与人消灾,用的是摩托的MX1,只好自己写了,界说了#MACH_TYPE_MX1,当然,还要写一个描绘渠道的数据结构:
MACHINE_START(MX1ADS,”MotorolaMX1ADS”)
MAINTAINER(“SPSMotorola”)
BOOT_MEM(0x08000000,0x00200000,0xf0200000)
FIXUP(mx1ads_fixup)
MAPIO(mx1ads_map_io)
INITIRQ(mx1ads_init_irq)
MACHINE_END
看起来怪怪的,但现在咱们只需知道他界说了根本的内存映象:RAM从0x08000000开端,i/o空间从0x00200000开端,i/o空间映射到虚拟地址空间
0xf0200000开端处。摩托的芯片i/o和内存是一致编址的。
其他的项,鄙人面的初始化进程中会逐一介绍到。
好了好了,再看下面的指令:
movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode//设置为SVC形式,答应中止和快速中止
//此处设定体系的作业状况,arm有7种状况
//每种状况有自己的仓库
msrcpsr_c,r0@andallirqsdiabled
bl__lookup_processor_type
//界说处理器相关信息,如value,mask,mmuflags,
//放在proc.info段中
//__lookup_processor_type获得这些信息,鄙人面
//__lookup_architecture_type顶用
这一段是查询处理器的品种,咱们知道arm有arm7,arm9等类型,怎样区别呢?
在arm协处理器中有一个只读寄存器,寄存处理器相关信息。__lookup_processor_type将回来如下的结构:
__arm920_proc_inf
.long0x41009200//CPUid
.long0xff00fff0//cpumask
.long0x00000c1e@mmuflags
b__arm920_setup
.longcpu_arch_name
.longcpu_elf_name
.longHWCAP_SWP|HWCAP_HALF|HWCAP_26BIT
.longcpu_arm920_info
.longarm920_processor_functions
榜首项是CPUid,将与协处理器中读出的id作比较,其他的都是与处理器相关的
信息,到下面初始化的进程中自然会用到。。
查询到了处理器类型和体系的内存映像后就要进入初始化进程中比较要害的一步了,开端设置mmu,但首要要设置一个暂时的内核页表,映射4m的内存,这在初始化进程中是满足了:
//r5=08000000ram开端地址r6=00200000io地址,r7=f0200000虚io
teqr7,#0@invalidarchitecture?
moveqr0,#a@yes,errora
beq__error
bl__create_page_tables
其间__create_page_tables为:
__create_page_tables:
pgtblr4
//r4=08004000暂时页表的开端地址
//r5=08000000,ram的开端地址
//r6=00200000,i/o寄存器空间的开端地址
//r7=00003c08
//r8=00000c1e
//thepagetablein08004000isjusttempbasepage,wheninit_taskssweaper_page_dirready,
//thetemppagewillbeuseless
//thehigh12bitofvirtualaddressisbasetableindex,soweneed4kx4=16ktempbasepage,
movr0,r4
movr3,#0
addr2,r0,#0x4000@16kofpagetable
1:strr3,[r0],#4@Clearpagetable
strr3,[r0],#4
strr3,[r0],#4
strr3,[r0],#4
teqr0,r2
bne1b
/*
*CreateidentitymappingforfirstMBofkernel.
*Thisismarkedcacheableandbufferable.
*
*Theidentitymappingwillberemovedby
*/
//因为linux编译的地址是0xC0008000,load的地址是0x08008000,咱们需要将虚地址0xC0008000映射到0800800一段
//一同,因为部分代码也要直接拜访0x08008000,所以0x08008000对应的表项也要填充
//页表中的表象为section,AP=11表明任何形式下可拜访,domain为0。
addr3,r8,r5@mmuflags+startofRAM
//r3=08000c1e
addr0,r4,r5,lsr#18
//r0=08004200
strr3,[r0]@identitymapping
//*08004200=08000c1e0x200表象对应的是08000000的1m
/*
*Nowsetupthepagetablesforourkerneldirect
*mappedregion.WeroundTEXTADDRdowntothe
*nearestmegabyteboundary.
*/
//下面是映射4M
addr0,r4,#(TEXTADDR&0xfff00000)>>18@startofkernel
//r0=r4+0x3000=08004000+3000=08007000
strr3,[r0],#4@PAGE_OFFSET+0MB
//*08007004=08000c1e
addr3,r3,#1<<20
//r3=08100c1e
strr3,[r0],#4@PAGE_OFFSET+1MB
//*08007008=08100c1e
addr3,r3,#1<<20
strr3,[r0],#4
//*0800700c=08200c1e@PAGE_OFFSET+2MB
addr3,r3,#1<<20
strr3,[r0],#4@PAGE_OFFSET+3MB
//*08007010=08300c1e
bicr8,r8,#0x0c@turnoffcacheable
//r8=00000c12@andbufferablebits
movpc,lr//子程序回来。
下一回就要开端翻开mmu的操作了
上回书讲到现已设置好了内核的页表,然后要跳转到__arm920_setup,
这个函数在arch/arm/mm/proc-arm929.s
__arm920_setup:
movr0,#0
mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4
mcrp15,0,r0,c7,c10,4@drainwritebufferonv4
mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4
mcrp15,0,r4,c2,c0@loadpagetablepointer
movr0,#0x1f@Domains0,1=client
mcrp15,0,r0,c3,c0@loaddomainaccessregister
mrcp15,0,r0,c1,c0@getcontrolregisterv4
/*
*Clearoutunwantedbits(thenputtheminifweneedthem)
*/
@VIZFRSBLDPWCAM
bicr0,r0,#0x0e00
bicr0,r0,#0x0002
bicr0,r0,#0x000c
bicr0,r0,#0x1000@…0000…..000.
/*
*Turnonwhatwewant
*/
orrr0,r0,#0x0031
orrr0,r0,#0x2100@..1….1..11…1
#ifdefCONFIG_CPU_ARM920_D_CACHE_ON
orrr0,r0,#0x0004@………….1..
#endif
#ifdefCONFIG_CPU_ARM920_I_CACHE_ON
orrr0,r0,#0x1000@…1…………
#endif
movpc,lr
这一段首要封闭i,dcache,铲除writebuffer,然后设置页目录地址,设置
domain的维护,在上节中,留意到页目录项的domain都是0,domain寄存器中
的domain0对应的是0b11,表明拜访形式为manager,不受约束。
接下来设置操控寄存器,翻开d,icache和mmu
留意arm的dcache有必要和mmu一同翻开,而icache能够独自翻开
其实,cache和mmu的联系实在是严密,每一个页表项都有标志标明是否是
cacheable的,能够说原本便是规划一同运用的
最终,自函数回来后,有一句
mcrp15,0,r0,c1,c0
使设置收效。
上回咱们讲到arm靠初始化完结了,翻开了cache,
到此为止,汇编部分的初始化代码就差不多了,最终还有几件工作做:
1。初始化BSS段,悉数清零,BSS是大局变量区域。
2。保存与体系相关的信息:如
.longSYMBOL_NAME(compat)
.longSYMBOL_NAME(__bss_start)
.longSYMBOL_NAME(_end)
.longSYMBOL_NAME(processor_id)
.longSYMBOL_NAME(__machine_arch_type)
.longSYMBOL_NAME(cr_alignment)
.longSYMBOL_NAME(init_task_union)+8192
不必讲,咱们一看就理解意思
3。从头设置仓库指针,指向init_task的仓库。init_task是体系的榜首个使命,init_task的仓库在taskstructure的后8K,咱们后边会看到。
4。最终就要跳到C代码的start_kernel。
bSYMBOL_NAME(start_kernel)
现在让咱们来回想一下现在的体系状况:
暂时页表现已树立,在0X08004000处,映射了4M,虚地址0XC000000被映射到0X08000000.
CACHE,MMU都现已翻开。
仓库用的是使命init_task的仓库。