arm 嵌入式LINUX啟動過程(1)
LINUX啟動過程
首先,portinglinux的時候要規(guī)劃內存影像,如小弟的系統(tǒng)有64mSDRAM,
地址從0x08000000-0x0bffffff,32mflash,地址從0x0c000000-0x0dffffff.
規(guī)劃如下: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函數(shù),當C代碼執(zhí)行完后,還要調用
//下面的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函數(shù)比較長,讓我們分段慢慢看。
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開發(fā)平臺增加usb下載接口“。
如果不選,直接回車,就開始把整個linux的內核拷貝到SDRAM中運行。
列位看官,可能有人要問,在flashloader中不是已經初始化過sdram控制器了嗎?怎么init();中還要初始化呢,各位有所不知,小弟用的是syncflash,
可以直接使用sdram控制器的接口,切記:在flash中運行的代碼是不能初始化連接flash的sdram控制器的,不然絕對死掉了。所以,當程序在flash中運行的時候,去初始化sdram,而現(xiàn)在在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的一些全局數(shù)據結構,如內核頁表,arm的頁目錄要有16k大。
我們知道,linux內核啟動的時候可以傳入參數(shù),如在PC上,如果使用LILO,
當出現(xiàn)LILO:,我們可以輸入root=/dev/hda1.或mem=128M等指定文件系統(tǒng)的設備或內存大小,在嵌入式系統(tǒng)上,參數(shù)的傳入是要靠bootloader完成的,
pMem=(char*)0x083FF000;//參數(shù)字符串的目標存放地址
pCmdLine=(char*)&cmdLine;//定義的靜態(tài)字符串
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參數(shù)調用的順序是從左到右R0開始,所以R0是KERNKEL的地址,
r1是參數(shù)字符串的地址:
到此為止,為linux引導做的準備工作就結束了,下一回我們就正式進入linux的代碼。
好,從本節(jié)開始,我們走過了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映射過去呢?
因為不通的系統(tǒng)有不通的內存影像,所以,LINUX約定,內核代碼開始的時候,
R1放的是系統(tǒng)目標平臺的代號,對于一些常見的,標準的平臺,內核已經提供了支持,只要在編譯的時候選中就行了,例如對X86平臺,內核是從物理地址1M開始映射的。如果老兄是自己攢的平臺,只好麻煩你自己寫了。
小弟拿人錢財,與人消災,用的是摩托的MX1,只好自己寫了,定義了#MACH_TYPE_MX1,當然,還要寫一個描述平臺的數(shù)據結構:
MACHINE_START(MX1ADS,"MotorolaMX1ADS")
MAINTAINER("SPSMotorola")
BOOT_MEM(0x08000000,0x00200000,0xf0200000)
FIXUP(mx1ads_fixup)
MAPIO(mx1ads_map_io)
INITIRQ(mx1ads_init_irq)
MACHINE_END
看起來怪怪的,但現(xiàn)在大家只要知道他定義了基本的內存映象:RAM從0x08000000開始,i/o空間從0x00200000開始,i/o空間映射到虛擬地址空間
0xf0200000開始處。摩托的芯片i/o和內存是統(tǒng)一編址的。
其他的項,在下面的初始化過程中會逐個介紹到。
好了好了,再看下面的指令:
movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode//設置為SVC模式,允許中斷和快速中斷
//此處設定系統(tǒng)的工作狀態(tài),arm有7種狀態(tài)
//每種狀態(tài)有自己的堆棧
msrcpsr_c,r0@andallirqsdiabled
bl__lookup_processor_type
//定義處理器相關信息,如value,mask,mmuflags,
//放在proc.info段中
//__lookup_processor_type取得這些信息,在下面
//__lookup_architecture_type中用
這一段是查詢處理器的種類,大家知道arm有arm7,arm9等類型,如何區(qū)分呢?
在arm協(xié)處理器中有一個只讀寄存器,存放處理器相關信息。__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,將與協(xié)處理器中讀出的id作比較,其余的都是與處理器相關的
信息,到下面初始化的過程中自然會用到。。
查詢到了處理器類型和系統(tǒng)的內存映像后就要進入初始化過程中比較關鍵的一步了,開始設置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,
這個函數(shù)在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的保護,在上節(jié)中,注意到頁目錄項的domain都是0,domain寄存器中
的domain0對應的是0b11,表示訪問模式為manager,不受限制。
接下來設置控制寄存器,打開d,icache和mmu
注意arm的dcache必須和mmu一起打開,而icache可以單獨打開
其實,cache和mmu的關系實在是緊密,每一個頁表項都有標志標示是否是
cacheable的,可以說本來就是設計一起使用的
最后,自函數(shù)返回后,有一句
mcrp15,0,r0,c1,c0
使設置生效。
上回我們講到arm靠初始化完成了,打開了cache,
到此為止,匯編部分的初始化代碼就差不多了,最后還有幾件事情做:
1。初始化BSS段,全部清零,BSS是全局變量區(qū)域。
2。保存與系統(tǒng)相關的信息:如
.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是系統(tǒng)的第一個任務,init_task的堆棧在taskstructure的后8K,我們后面會看到。
4。最后就要跳到C代碼的start_kernel。
bSYMBOL_NAME(start_kernel)
現(xiàn)在讓我們來回憶一下目前的系統(tǒng)狀態(tài):
臨時頁表已經建立,在0X08004000處,映射了4M,虛地址0XC000000被映射到0X08000000.
CACHE,MMU都已經打開。
堆棧用的是任務init_task的堆棧。
評論