Linux bootloader全程詳解(ARM S3C2410)
寫在前面:這篇文章已經(jīng)寫的很詳細(xì)了。主要參考的就是正果過程,如果要是移植bootloader的話應(yīng)該找一套現(xiàn)成的源碼,至少我是這么認(rèn)為的,雖然我還沒有移植過。
網(wǎng)上關(guān)于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比較龐大的程序,讀起來不太方便,編譯出的文件也比較大,而且更多的是面向開發(fā)用的引導(dǎo)代碼,做成產(chǎn)品時還要裁減,這一定程度影響了開發(fā)速度,對初學(xué)者學(xué)習(xí)開銷也比較大,在此分析一種簡單的BOOTLOADER,是在三星公司提供的S3C2410BOOTLOADER上稍微修改后的結(jié)果,編譯出來的文件大小不超過4k,希望對大家有所幫助.
1.幾個重要的概念
COMPRESSED KERNEL and DECOMPRESSED KERNEL
壓縮后的KERNEL,按照文檔資料,現(xiàn)在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解壓器.因此要在ram分配時給壓縮和解壓的KERNEL提供足夠空間,這樣它們不會相互覆蓋.
當(dāng)執(zhí)行指令跳轉(zhuǎn)到COMPRESSED KERNEL后,解壓器就開始工作,如果解壓器探測到解壓的代碼會覆蓋掉COMPRESSED KERNEL,那它會直接跳到COMPRESSED KERNEL后存放數(shù)據(jù),并且重新定位KERNEL,所以如果沒有足夠空間,就會出錯.
Jffs2 File System
可以使armlinux應(yīng)用中產(chǎn)生的數(shù)據(jù)保存在FLASH上,我的板子還沒用到這個.
RAMDISK
使用RAMDISK可以使ROOT FILE SYSTEM在沒有其他設(shè)備的情況下啟動.一般有兩種加載方式,我就介紹最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把這個地址通過啟動參數(shù)的方式ATAG_INITRD2傳遞給KERNEL.具體看代碼分析.
啟動參數(shù)(摘自IBM developer)
在調(diào)用內(nèi)核之前,應(yīng)該作一步準(zhǔn)備工作,即:設(shè)置Linux內(nèi)核的啟動參數(shù)。Linux 2.4.x以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來傳遞啟動參數(shù)。啟動參數(shù)標(biāo)記列表以標(biāo)記ATAG_CORE開始,以標(biāo)記ATAG_NONE結(jié)束。每個標(biāo)記由標(biāo)識被傳遞參數(shù)的tag_header結(jié)構(gòu)以及隨后的參數(shù)值數(shù)據(jù)結(jié)構(gòu)來組成。數(shù)據(jù)結(jié)構(gòu)tag和tag_header定義在Linux內(nèi)核源碼的include/asm/setup.h頭文件中.
在嵌入式Linux系統(tǒng)中,通常需要由BOOTLOADER設(shè)置的常見啟動參數(shù)有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
(注)參數(shù)也可以用COMMANDLINE來設(shè)定,在我的BOOTLOADER里,我兩種都用了.
2.開發(fā)環(huán)境和開發(fā)板配置:
CPU:S3C2410,BANK6上有64M的SDRAM(兩塊),BANK0上有32MNOR FLASH,串口當(dāng)然是逃不掉的.這樣,按照數(shù)據(jù)手冊,地址分配如下:
0x4000_0000開始是4k的片內(nèi)DRAM.
0x0000_0000開始是32MFLASH 16bit寬度
0x3000_0000開始是64MSDRAM 32bit寬度
注意:控制寄存器中的BANK6和BANK7部分必須相同.
0x4000_0000(片內(nèi)DRAM)存放4k以內(nèi)的BOOTLOADER IMAGE
0x3000_0100開始存放啟動參數(shù)
0x3120_0000存放COMPRESSED KERNEL IMAGE
0x3200_0000存放COMPRESSED RAMDISK
0x3000_8000指定為DECOMPRESSED KERNEL IMAGE ADDRESS
0x3040_0000指定為DECOMPRESSED RAMDISK IMAGE ADDRESS
開發(fā)環(huán)境:Redhat Linux,armgcc toolchain, armlinux KERNEL
如何建立armgcc的編譯環(huán)境:建議使用toolchain,而不要自己去編譯armgcc,偶試過好多次,都以失敗告終.
先下載arm-gcc 3.3.2 toolchain
將arm-linux-gcc-3.3.2.tar.bz2解壓到/toolchain
# tar jxvf arm-linux-gcc-3.3.2.tar.bz2
# mv /usr/local/arm/3.3.2 /toolchain
在makefile中在把a(bǔ)rch=arm CROSS_COMPILE設(shè)置成toolchain的路徑
還有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否則庫函數(shù)就不能用了
3.啟動方式:
可以放在FLASH里啟動,或者用Jtag仿真器.由于使用NOR FLASH,根據(jù)2410的手冊,片內(nèi)的4K DRAM在不需要設(shè)置便可以直接使用,而其他存儲器必須先初始化,比如告訴memory controller,BANK6里有兩塊SDRAM,數(shù)據(jù)寬度是32bit,= =.否則memory control會按照復(fù)位后的默認(rèn)值來處理存儲器.這樣讀寫就會產(chǎn)生錯誤.
所以第一步,通過仿真器把執(zhí)行代碼放到0x4000_0000,(在編譯的時候,設(shè)定TEXT_BAS
E=0x40000000)。
第二步,通過AxD把linux KERNEL IMAGE放到目標(biāo)地址(SDRAM)中,等待調(diào)用
第三步,執(zhí)行BOOTLOADER代碼,從串口得到調(diào)試數(shù)據(jù),引導(dǎo)armlinux
4.代碼分析
講了那么多執(zhí)行的步驟,是想讓大家對啟動有個大概印象,接著就是BOOTLOADER內(nèi)部的代碼分析了,BOOTLOADER文章內(nèi)容網(wǎng)上很多,我這里精簡了下,刪除了不必要的功能.
BOOTLOADER一般分為2部分,匯編部分和c語言部分,匯編部分執(zhí)行簡單的硬件初始化,C部分負(fù)責(zé)復(fù)制數(shù)據(jù),設(shè)置啟動參數(shù),串口通信等功能.
BOOTLOADER的生命周期:
1.初始化硬件,比如設(shè)置UART(至少設(shè)置一個),檢測存儲器= =.
2.設(shè)置啟動參數(shù),這是為了告訴內(nèi)核硬件的信息,比如用哪個啟動界面,波特率= =.
3.跳轉(zhuǎn)到Linux KERNEL的首地址.
4.消亡
當(dāng)然,在引導(dǎo)階段,象vivi等,都用虛地址,如果你嫌煩的話,就用實(shí)地址,都一樣.
我們來看代碼:
2410init.s
.global _start//開始執(zhí)行處
_start:
//下面是中斷向量
b reset @ Supervisor Mode//重新啟動后的跳轉(zhuǎn)
……
……
reset:
ldr r0,=WTCON /WTCON地址為53000000,watchdog的控制寄存器*/
ldr r1,=0x0 /*關(guān)watchdog*/
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff /*屏蔽所有中斷*/
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x3ff /*子中斷也一樣*/
str r1,[r0]
/*Initialize Ports...for display LED.*/
ldr r0, =GPFCON
ldr r1, =0x55aa
str r1, [r0]
ldr r0, =GPFUP
ldr r1, =0xff
str r1, [r0]
ldr r0,=GPFDAT
ldr r1,=POWEROFFLED1
str r1,[r0]
/* Setup clock Divider control register
* you must configure CLKDIVN before LOCKTIME or MPLL UPLL
* because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflict
nop
* FCLK:HCLK:PCLK = 1:2:4 in this case
*/
ldr r0,=CLKDIVN
ldr r1,=0x3
str r1,[r0]
/*To reduce PLL lock time, adjust the LOCKTIME register. */
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
/*Configure MPLL */
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz
str r1,[r0]
ldr r1,=GSTATUS2
ldr r10,[r1]
tst r10,#OFFRST
bne 1000f
//以上這段,我沒動,就用三星寫的了,下面是主要要改的地方
/* MEMORY C0NTROLLER(MC)設(shè)置*/
add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放著MC初始化要用到的數(shù)據(jù)
ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址
add r2,r0,#52 //復(fù)制次數(shù),偏移52字
1: //按照偏移量進(jìn)行循環(huán)復(fù)制
ldr r3,[r0],#4
str r3,[r1],#4
cmp r2,r0
bne 1b
.align 2
MCDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)
+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
上面這行就是BWSCON的數(shù)據(jù),具體參數(shù)意義如下:
需要更改設(shè)置DW6和DW7都設(shè)置成10,即32bit,DW0設(shè)置成01,即16bit
下面都是每個BANK的控制器數(shù)據(jù),大都是時鐘相關(guān),可以用默認(rèn)值,設(shè)置完MC后,就跳到調(diào)用main函數(shù)的部分
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)
+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)
+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)
+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)
+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)
+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)
+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0xB2 /* REFRESH Control Register */
.word 0x30 /* BANKSIZE Register : Burst Mode */
.word 0x30 /* SDRAM Mode Register */
.align 2
.global call_main //調(diào)用main函數(shù),函數(shù)參數(shù)都為0
call_main:
ldr sp,STACK_START
mov fp,#0 /* no previous frame, so fp=0*/
mov a1, #0 /* set argc to 0*/
mov a2, #0 /* set argv to NUL*/
bl main /* call main*/
STACK_START:
.word STACK_BASE
undefined_instruction:
software_interrupt:
prefetch_abort:
data_abort:
not_used:
irq:
fiq:
/*以上是主要的匯編部分,實(shí)現(xiàn)了時鐘設(shè)置,串口設(shè)置watchdog關(guān)閉,中斷關(guān)閉功能(如果有需要還可以降頻使用),然后轉(zhuǎn)入main*/
2410init.c file
int main(int argc,char **argv)
{
u32 test = 0;
//壓縮后的IMAGE地址
void (*theKERNEL)(int zero, int arch, unsigned long params_addr) =
(void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL_BASE;
int i,k=0;
// downPt=(RAM_COMPRESSED_KERNEL_BASE);
chkBs=(_RAM_STARTADDRESS);//SDRAM開始的地方
// fromPt=(FLASH_LINUXKERNEL);
MMU_EnableICache();
ChangeClockDivider(1,1); // 1:2:4
ChangeMPllvalue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz
Port_Init();//設(shè)置I/O端口,在使用com口前,必須調(diào)用這個函數(shù),否則通信芯片根本得不到數(shù)據(jù)
Uart_Init(PCLK, 115200);//PCLK使用默認(rèn)的200000,撥特率115200
/*******************(檢查ram空間)*******************/
Uart_SendString("ntLinux S3C2410 Nor BOOTLOADERn");
Uart_SendString("ntChecking SDRAM 2410loader.c...n");
for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//