新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > arm 嵌入式LINUX啟動(dòng)過(guò)程(1)

arm 嵌入式LINUX啟動(dòng)過(guò)程(1)

作者: 時(shí)間:2016-11-09 來(lái)源:網(wǎng)絡(luò) 收藏
一位大師級(jí)的人物寫(xiě)的,不看要后悔的喲!!
LINUX啟動(dòng)過(guò)程

首先,portinglinux的時(shí)候要規(guī)劃內(nèi)存影像,如小弟的系統(tǒng)有64mSDRAM,
地址從0x08000000-0x0bffffff,32mflash,地址從0x0c000000-0x0dffffff.
規(guī)劃如下:bootloader,linuxkernel,rootdisk放在flash里。
具體從0x0c000000開(kāi)始的第一個(gè)1M放bootloader,
0x0c100000開(kāi)始的2m放linuxkernel,從0x0c300000開(kāi)始都給rootdisk。

啟動(dòng):
首先,啟動(dòng)后arm920T將地址0x0c000000映射到0(可通過(guò)跳線(xiàn)設(shè)置),
實(shí)際上從0x0c000000啟動(dòng),進(jìn)入我們的bootloader,但由于flash速度慢,
所以bootloader前面有一小段程序把bootloader拷貝到SDRAM中的0x0AFE0100,
再?gòu)?x08000000運(yùn)行bootloader,我們叫這段小程序?yàn)閒lashloader,
flashloader必須要首先初始化SDRAM,不然往那放那些東東:

.equSOURCE,0x0C000100bootloader的存放地址
.equTARGET,0x0AFE0100目標(biāo)地址
.equSDCTL0,0x221000SDRAM控制器寄存器
//sizeisstoredinlocation0x0C0000FC

.global_start
_start://入口點(diǎn)

//;*
//;*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

上回書(shū)說(shuō)到flashloader把bootloaderload到0x0AFE0100,然回跳了過(guò)去,
其實(shí)0x0AFE0100就是燒在flash0x0C000100中的真正的bootloader:

bootloader有幾個(gè)文件組成,先是START.s,也是唯一的一個(gè)匯編程序,其余的都是C寫(xiě)成的,START.s主要初始化堆棧:

_start:
ldrr1,=StackInit
ldrsp,[r1]
bmain
//此處我們跳到了C代碼的main函數(shù),當(dāng)C代碼執(zhí)行完后,還要調(diào)用
//下面的JumpToKernel0x跳到LINXUkernel運(yùn)行

.equStackInitvalue,__end_data+0x1000//4K__end_data在連結(jié)腳本中指定

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ù)比較長(zhǎng),讓我們分段慢慢看。

intmain()
{
U32*pSource,*pDestin,count;
U8countDown,bootOption;
U32delayCount;
U32fileSize,i;
charc;
char*pCmdLine;
char*pMem;

init();//初始化FLASH控制器和CPU時(shí)鐘

EUARTinit();//串口初始化
EUARTputString("/n/nDBMX1LinuxBootloaderver0.2.0/n");
EUARTputString("Copyright(C)2002MotorolaLtd./n/n");
EUARTputString((U8*)cmdLine);
EUARTputString("/n/n");

EUARTputString("Pressanykeyforalternateboot-upoptions...");

小弟的bootloader主要干這么幾件事:init();初始化硬件,打印一些信息和提供一些操作選項(xiàng):
0.Programbootloaderimage
1.Programkernelimage
2.Programroot-diskimage
3.DownloadkernelandbootfromRAM
4.Downloadkernelandbootwithver0.1.xbootloaderformat
5.Bootaver0.1.xkernel
6.Bootwithadifferentcommandline

也就是說(shuō),可以在bootloader里選擇重新下載kernel,rootdisk并寫(xiě)入flash,
下載的方法是用usb連接,10m的rootdisk也就刷的一下。關(guān)于usb下載的討論請(qǐng)參看先前的貼子“為arm開(kāi)發(fā)平臺(tái)增加usb下載接口“。
如果不選,直接回車(chē),就開(kāi)始把整個(gè)linux的內(nèi)核拷貝到SDRAM中運(yùn)行。

列位看官,可能有人要問(wèn),在flashloader中不是已經(jīng)初始化過(guò)sdram控制器了嗎?怎么init();中還要初始化呢,各位有所不知,小弟用的是syncflash,
可以直接使用sdram控制器的接口,切記:在flash中運(yùn)行的代碼是不能初始化連接flash的sdram控制器的,不然絕對(duì)死掉了。所以,當(dāng)程序在flash中運(yùn)行的時(shí)候,去初始化sdram,而現(xiàn)在在sdram中運(yùn)行,可放心大膽地初始化flash了,主要是設(shè)定字寬,行列延時(shí),因?yàn)槿笔《际亲畲蟮摹?br />
另外,如果列位看官的cpu有足夠的片內(nèi)ram,完全可以先把bootloader放在片內(nèi)ram,干完一切后再跳到LINUX,小弟著也是不得已而為之啊。

如果直接輸入回車(chē),進(jìn)入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");

這一段沒(méi)有什么可說(shuō)的,運(yùn)行完后kernel就在0x08008000了,至于為什么要
空出0x8000的一段,主要是放kelnel的一些全局?jǐn)?shù)據(jù)結(jié)構(gòu),如內(nèi)核頁(yè)表,arm的頁(yè)目錄要有16k大。

我們知道,linux內(nèi)核啟動(dòng)的時(shí)候可以傳入?yún)?shù),如在PC上,如果使用LILO,
當(dāng)出現(xiàn)LILO:,我們可以輸入root=/dev/hda1.或mem=128M等指定文件系統(tǒng)的設(shè)備或內(nèi)存大小,在嵌入式系統(tǒng)上,參數(shù)的傳入是要靠bootloader完成的,

pMem=(char*)0x083FF000;//參數(shù)字符串的目標(biāo)存放地址
pCmdLine=(char*)&cmdLine;//定義的靜態(tài)字符串
while((*(pMem++)=*(pCmdLine++))!=0);//拷貝

JumpToKernel((void*)0x8008000,0x083FF000)//跳轉(zhuǎn)到內(nèi)核

return(0);
JumpToKernel在前文中的start.S定義過(guò):

JumpToKernel:
//jumptothecopycode(gettheargumentsright)
movpc,r0

.globalJumpToKernel0x
//r0=jumpaddress
//r1=argumentstouse(thesegetshifted)

由于arm-GCC的c參數(shù)調(diào)用的順序是從左到右R0開(kāi)始,所以R0是KERNKEL的地址,
r1是參數(shù)字符串的地址:

到此為止,為linux引導(dǎo)做的準(zhǔn)備工作就結(jié)束了,下一回我們就正式進(jìn)入linux的代碼。

好,從本節(jié)開(kāi)始,我們走過(guò)了bootloader的漫長(zhǎng)征途,開(kāi)始進(jìn)入linux的內(nèi)核:
說(shuō)實(shí)話(huà),linux寶典的確高深莫測(cè),洋人花了十幾年修煉,各種內(nèi)功心法層處不窮。有些地方反復(fù)推敲也領(lǐng)悟不了其中奧妙,煉不到第九重啊。。

linux的入口是一段匯編代碼,用于基本的硬件設(shè)置和建立臨時(shí)頁(yè)表,對(duì)于
ARMLINUX是linux/arch/arm/kernle/head-armv.S,走!

#ifdefined(CONFIG_MX1)
movr1,#MACH_TYPE_MX1
#endif

這第一句話(huà)好像就讓人看不懂,好像葵花寶典開(kāi)頭的八個(gè)字:欲練神功。。。。

那來(lái)的MACH_TYPE_MX1?其實(shí),在head-armv.S
中的一項(xiàng)重要工作就是設(shè)置內(nèi)核的臨時(shí)頁(yè)表,不然mmu開(kāi)起來(lái)也玩不轉(zhuǎn),但是內(nèi)核怎么知道如何映射內(nèi)存呢?linux的內(nèi)核將映射到虛地址0xCxxxxxxx處,但他怎么知道把哪一片ram映射過(guò)去呢?

因?yàn)椴煌ǖ南到y(tǒng)有不通的內(nèi)存影像,所以,LINUX約定,內(nèi)核代碼開(kāi)始的時(shí)候,
R1放的是系統(tǒng)目標(biāo)平臺(tái)的代號(hào),對(duì)于一些常見(jiàn)的,標(biāo)準(zhǔn)的平臺(tái),內(nèi)核已經(jīng)提供了支持,只要在編譯的時(shí)候選中就行了,例如對(duì)X86平臺(tái),內(nèi)核是從物理地址1M開(kāi)始映射的。如果老兄是自己攢的平臺(tái),只好麻煩你自己寫(xiě)了。

小弟拿人錢(qián)財(cái),與人消災(zāi),用的是摩托的MX1,只好自己寫(xiě)了,定義了#MACH_TYPE_MX1,當(dāng)然,還要寫(xiě)一個(gè)描述平臺(tái)的數(shù)據(jù)結(jié)構(gòu):

MACHINE_START(MX1ADS,"MotorolaMX1ADS")
MAINTAINER("SPSMotorola")

BOOT_MEM(0x08000000,0x00200000,0xf0200000)

FIXUP(mx1ads_fixup)
MAPIO(mx1ads_map_io)
INITIRQ(mx1ads_init_irq)
MACHINE_END

看起來(lái)怪怪的,但現(xiàn)在大家只要知道他定義了基本的內(nèi)存映象:RAM從0x08000000開(kāi)始,i/o空間從0x00200000開(kāi)始,i/o空間映射到虛擬地址空間
0xf0200000開(kāi)始處。摩托的芯片i/o和內(nèi)存是統(tǒng)一編址的。
其他的項(xiàng),在下面的初始化過(guò)程中會(huì)逐個(gè)介紹到。

好了好了,再看下面的指令:

movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode//設(shè)置為SVC模式,允許中斷和快速中斷
//此處設(shè)定系統(tǒng)的工作狀態(tài),arm有7種狀態(tài)
//每種狀態(tài)有自己的堆棧

msrcpsr_c,r0@andallirqsdiabled
bl__lookup_processor_type

//定義處理器相關(guān)信息,如value,mask,mmuflags,
//放在proc.info段中
//__lookup_processor_type取得這些信息,在下面
//__lookup_architecture_type中用

這一段是查詢(xún)處理器的種類(lèi),大家知道arm有arm7,arm9等類(lèi)型,如何區(qū)分呢?
在arm協(xié)處理器中有一個(gè)只讀寄存器,存放處理器相關(guān)信息。__lookup_processor_type將返回如下的結(jié)構(gòu):

__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

第一項(xiàng)是CPUid,將與協(xié)處理器中讀出的id作比較,其余的都是與處理器相關(guān)的
信息,到下面初始化的過(guò)程中自然會(huì)用到。。

查詢(xún)到了處理器類(lèi)型和系統(tǒng)的內(nèi)存映像后就要進(jìn)入初始化過(guò)程中比較關(guān)鍵的一步了,開(kāi)始設(shè)置mmu,但首先要設(shè)置一個(gè)臨時(shí)的內(nèi)核頁(yè)表,映射4m的內(nèi)存,這在初始化過(guò)程中是足夠了:

//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臨時(shí)頁(yè)表的起始地址
//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一段
//同時(shí),由于部分代碼也要直接訪(fǎng)問(wèn)0x08008000,所以0x08008000對(duì)應(yīng)的表項(xiàng)也要填充
//頁(yè)表中的表象為section,AP=11表示任何模式下可訪(fǎng)問(wèn),domain為0。
addr3,r8,r5@mmuflags+startofRAM
//r3=08000c1e
addr0,r4,r5,lsr#18
//r0=08004200
strr3,[r0]@identitymapping
//*08004200=08000c1e0x200表象對(duì)應(yīng)的是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//子程序返回。
下一回就要開(kāi)始打開(kāi)mmu的操作了

上回書(shū)講到已經(jīng)設(shè)置好了內(nèi)核的頁(yè)表,然后要跳轉(zhuǎn)到__arm920_setup,
這個(gè)函數(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

這一段首先關(guān)閉i,dcache,清除writebuffer,然后設(shè)置頁(yè)目錄地址,設(shè)置
domain的保護(hù),在上節(jié)中,注意到頁(yè)目錄項(xiàng)的domain都是0,domain寄存器中
的domain0對(duì)應(yīng)的是0b11,表示訪(fǎng)問(wèn)模式為manager,不受限制。

接下來(lái)設(shè)置控制寄存器,打開(kāi)d,icache和mmu
注意arm的dcache必須和mmu一起打開(kāi),而icache可以單獨(dú)打開(kāi)

其實(shí),cache和mmu的關(guān)系實(shí)在是緊密,每一個(gè)頁(yè)表項(xiàng)都有標(biāo)志標(biāo)示是否是
cacheable的,可以說(shuō)本來(lái)就是設(shè)計(jì)一起使用的

最后,自函數(shù)返回后,有一句
mcrp15,0,r0,c1,c0
使設(shè)置生效。

上回我們講到arm靠初始化完成了,打開(kāi)了cache,
到此為止,匯編部分的初始化代碼就差不多了,最后還有幾件事情做:

1。初始化BSS段,全部清零,BSS是全局變量區(qū)域。
2。保存與系統(tǒng)相關(guān)的信息:如
.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。重新設(shè)置堆棧指針,指向init_task的堆棧。init_task是系統(tǒng)的第一個(gè)任務(wù),init_task的堆棧在taskstructure的后8K,我們后面會(huì)看到。

4。最后就要跳到C代碼的start_kernel。
bSYMBOL_NAME(start_kernel)

現(xiàn)在讓我們來(lái)回憶一下目前的系統(tǒng)狀態(tài):
臨時(shí)頁(yè)表已經(jīng)建立,在0X08004000處,映射了4M,虛地址0XC000000被映射到0X08000000.
CACHE,MMU都已經(jīng)打開(kāi)。
堆棧用的是任務(wù)init_task的堆棧。


評(píng)論


技術(shù)專(zhuān)區(qū)

關(guān)閉