[TOC]
Bootloader啟動流程分析
??Bootloader的啟動過程可以分為單階段蜂大、多階段兩種。通常多階段的 Bootloader能提供更為復雜的功能以及更好的可移植性。從固態(tài)存儲設(shè)備上啟動的 Bootloader大多都是兩階段的啟動過程智蝠。第一階段使用匯編來實現(xiàn)票摇,它完成一些依賴于CPU體系結(jié)構(gòu)的初始化,并調(diào)用第二階段的代碼妙同;第二階段則通常使用C語言來實現(xiàn)射富,這樣可以實現(xiàn)更復雜的功能,而且代碼會有更好的可讀性和可移植性粥帚。
??一般而言辉浦,這兩個階段完成的功能可以如下分類:
Bootloader第一階段的功能
硬件設(shè)備初始化
??首先需要設(shè)置時鐘,設(shè)置MPLL(具體參見下面的FCLK HCLK PCLK 部分)茎辐。接著設(shè)置CLKDIVN地址為0x4C000014宪郊,寫入0x05,表示設(shè)置分頻系數(shù)為FCLK:HCLK:PCLK=1:4:8轨帜。接著,關(guān)閉看門狗锡搜,關(guān)中斷响鹃,啟動ICACHE,關(guān)閉DCACHE和TLB乎串,關(guān)閉MMU(ICACHE為指令緩存店枣,可以不關(guān)閉,指令直接操作的硬件叹誉,實際的物理地址鸯两。但是DCACHE就必須要關(guān)閉,此時MMU沒有使能长豁,虛擬地址映射不成功钧唐,sdram無法訪問,DCACHE無數(shù)據(jù))匠襟。start.s具體代碼如下:
/* 設(shè)置時鐘 */
ldr r0, =0x4c000014
// mov r1, #0x03;
mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
str r1, [r0]
/* 如果HDIVN非0钝侠,CPU的總線模式應該從“fast bus mode”變?yōu)椤癮synchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 讀出控制寄存器 */
orr r1, r1, #0xc0000000 /* 設(shè)置為“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 寫入控制寄存器 */
/* MPLLCON = S3C2440_MPLL_200MHZ */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]
/* 啟動ICACHE */
mrc p15, 0, r0, c1, c0, 0 @ read control reg
orr r0, r0, #(1<<12)
mcr p15, 0, r0, c1, c0, 0 @ write it back
??這里具體講下是如何設(shè)置FCLK HCLK PCLK。
??FCLK又稱為內(nèi)核時鐘酸舍,是提供給ARM920T 的時鐘帅韧。
??HCLK又稱為總線時鐘,是提供給用于存儲器控制器啃勉,中斷控制器忽舟,LCD 控制器,DMA 和 USB 主機模塊的 AHB總線(advanced high-performance bus)的時鐘淮阐。
??PCLK又稱為I/O接口時鐘叮阅,是提供給用于外設(shè)如WDT,IIS枝嘶,I2C帘饶,PWM 定時器,MMC/SD 接口群扶,ADC及刻,UART,GPIO竞阐,RTC 和SPI的 APB (advanced peripherals bus)總線的時鐘缴饭。
??S3C2440 FLCK值為400MHz,HCLK值為100MHz骆莹、PCLK值為50MHz颗搂。那么這些值通過什么方法計算出來呢?S3C2440上的時鐘源是12MHz幕垦,如果想讓CPU工作在更高頻率上丢氢,就需要通過PLL(鎖相環(huán))來提高主頻傅联。S3C2440上的PLL有兩種,一種是MPLL疚察,它是用來產(chǎn)生FCLK蒸走、HCLK、PCLK的高頻工作時鐘貌嫡;還有一種是UPLL比驻,用來為USB提供工作頻率。S3C2440時鐘體系如下:
??從時序圖中岛抄,我們可以看到别惦,上電之后,如果什么都不設(shè)置夫椭,F(xiàn)CLK和晶振的頻率相等掸掸。當設(shè)置PLL后,CPU并不是馬上就使用設(shè)置好的高頻時鐘益楼,而是有一段鎖定時間猾漫,在這段時間里点晴,CPU停止運行感凤,等12MHz變成高頻時鐘穩(wěn)定以后,整個系統(tǒng)再重新運行粒督。
??開啟MPLL的過程:
??1陪竿、設(shè)置LOCKTIME變頻鎖定時間
??2、設(shè)置FCLK與晶振輸入頻率(Fin)的倍數(shù)
??3屠橄、設(shè)置FCLK族跛,HCLK,PCLK三者之間的比例
??從手冊上可以看到锐墙,LOCKTIME的默認時間是0xFFFFFFFF礁哄,控制方法如圖:
??(剛設(shè)置好PLL時,系統(tǒng)認為這是PLL還沒穩(wěn)定溪北,所有這時不用PLL的時鐘桐绒,而用外部晶振做時鐘,將PLL鎖住之拨,過了LOCKTIME后認為PLL已經(jīng)穩(wěn)定了茉继,才使用PLL給系統(tǒng)提供時鐘)
??FCLK與Fin的倍數(shù)通過MPLLCON寄存器設(shè)置,三者之間有以下關(guān)系:
??MPLL(FCLK) = (2 * m * Fin)/(p*2^s)
??其中:m = MDIV + 8, p = PDIV + 2, s = SDIV
??PLL配置寄存器如圖:
??當設(shè)置完MPLL之后蚀乔,就會自動進入LockTime變頻鎖定期間烁竭,LOCKTIME之后,MPLL輸出穩(wěn)定時鐘頻率吉挣。
?? FCLK派撕、HCLK婉弹、PCLK的設(shè)置比例如圖:
??如果HDIV設(shè)置為非0,CPU的總線模式要進行改變终吼,默認情況下FCLK = HCLK马胧,CPU工作在fast bus mode快速總線模式下,HDIV設(shè)置為非0后衔峰, FCLK與HCLK不再相等佩脊,要將CPU改為asynchronous bus mod異步總線模式,可以通過下面的嵌入?yún)R編代碼實現(xiàn):
__asm__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 讀出控制寄存器 */
"orr r1, r1, #0xc0000000\n" /* 設(shè)置為“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0\n" /* 寫入控制寄存器 */
);
為加載 Bootloader的第二階段代碼準備RAM空間(初始化內(nèi)存空間)
??lowlevel_init中設(shè)置相應BANK地址垫卤,主要用來設(shè)置SDRAM威彰。內(nèi)存是被映射在了0x30000000-0x40000000的位置,即bank6與bank7穴肘。那么在內(nèi)存時序設(shè)置的時候歇盼,主要關(guān)心的,就是bank6與bank7评抚。當然豹缀,bank0也是需要關(guān)注的,因為它是啟動時慨代,啟動程序存放的位置邢笙。但是bank0是由OM[1:0],即板子上的那幾個小開關(guān)中的兩個來控制的侍匙,所以這里程序上是不用管它的氮惯。
SMRDATA:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000740 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
??接下來設(shè)置棧地址指向NAND,準備初始化NANDFLASH想暗。
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)//等于0x30000f80
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
bl nand_init_ll
版權(quán)聲明:本文為博主原創(chuàng)文章妇汗,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明说莫。
本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510
??初始化NANDFLASH杨箭,其中包括設(shè)置時序NFCONF,(參考芯片手冊和2440手冊設(shè)置nandflsh的啟動時序)储狭。TACLS表示的建立所用的時間互婿,TWRPH0表示nWE寫控制信號的持續(xù)時間,TWRPH1表示數(shù)據(jù)生效所用的時間晶密,什么時候可以讀數(shù)據(jù)擒悬。 最后就是使能NFCONT NAND Flash控制器,初始化ECC, 禁止片選稻艰。到這里懂牧,NANDFLASH的初始化就完成了。下面就可以進行重定位了。
void nand_init_ll(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
/* 設(shè)置時序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* 使能NAND Flash控制器, 初始化ECC, 禁止片選 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
復制 Bootloader的第二階段代碼到SDRAM空間中(重定位)
??首先判斷是NOR啟動還是NAND啟動僧凤,如果是NAND啟動就直接拷貝數(shù)據(jù)畜侦。拷貝代碼之前躯保,要傳遞給拷貝函數(shù)三個參數(shù)旋膳,源,目的途事,長度验懊。讀取NAND的話要參考芯片手冊的NAND讀取數(shù)據(jù)的時序,選中NAND尸变,發(fā)出讀命令义图,發(fā)出地址,發(fā)出讀命令召烂,判斷狀態(tài)碱工,讀取數(shù)據(jù),取消選中等奏夫。
bl copy_code_to_sdram
bl clear_bss //清除bss段(參考自制uboot章節(jié))
void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
int i = 0;
/* 如果是NOR啟動 */
if (isBootFromNorFlash())
{
while (i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
//nand_init();
nand_read_ll((unsigned int)src, dest, len);
}
}
void clear_bss(void)
{
extern int __bss_start, __bss_end__;
int *p = &__bss_start;
for (; p < &__bss_end__; p++)
*p = 0;
}
??最后要清除bss怕篷。bss段不占用空間,都是未初始化的全局變量或者已經(jīng)初始化為零的變量酗昼,本來就是零廊谓,直接清零就好。不清零的話未初始化的變量可能會存在未知的數(shù)值仔雷。
設(shè)置好棧
??設(shè)置棧跳轉(zhuǎn)到SDRAM執(zhí)行蹂析。
ldr pc,=call_board_init_f //絕對跳轉(zhuǎn),跳到SDRAM上執(zhí)行
跳轉(zhuǎn)到第二階段代碼的C入口點
??跳轉(zhuǎn)到SDRAM執(zhí)行剩下的程序舔示。
call_board_init_f:
.globl base_sp
base_sp:
.long 0
ldr r0,=0x00000000
bl board_init_f
/*unsigned int id 的值存在r0中碟婆,正好給board_init_r使用*/
ldr r1, =_TEXT_BASE
/*重新設(shè)置棧到之前的位置 指向原來addr_sp += 128;*/
ldr sp,base_sp
/*調(diào)用第二階段代碼*/
bl board_init_r
Bootloader第二階段的功能
初始化本階段要使用到的硬件設(shè)備
??為了方便開發(fā),至少要初始化一個串口以便程序員與 Bootloader進行交互惕稻。
檢測系統(tǒng)內(nèi)存映射( memory map)
??所謂檢測內(nèi)存映射竖共,就是確定板上使用了多少內(nèi)存、它們的地址空間是什么俺祠。由于嵌入式開發(fā)中 Bootloader多是針對某類板子進行編寫公给,所以可以根據(jù)板子的情況直接設(shè)置,不需要考慮可以適用于各類情況的復雜算法蜘渣。
將內(nèi)核映象和根文件系統(tǒng)映象從 Flash上讀到SDRAM空間中
??Flash上的內(nèi)核映象有可能是經(jīng)過壓縮的淌铐,在讀到SDRAM之后,還需要進行解壓蔫缸。當然腿准,對于有自解壓功能的內(nèi)核,不需要 Bootloader來解壓。將根文件系統(tǒng)映象復制到SDRAM中吐葱,這不是必需的街望。這取決于是什么類型的根文件系統(tǒng)以及內(nèi)核訪問它的方法。
??將內(nèi)核存放在適當?shù)奈恢煤蟮芘埽苯犹剿娜肟邳c即可調(diào)用內(nèi)核灾前。調(diào)用內(nèi)核之前,下列條件要滿足:
??(1)CPU寄存器的設(shè)置
??R0=0(規(guī)定)孟辑。
??R1=機器類型ID哎甲;對于ARM結(jié)構(gòu)的CPU,其機器類型ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動參數(shù)標記列表在RAM中起始基地址(下面會詳細介紹如何傳遞參數(shù))饲嗽。
??(2)CPU工作模式
??必須禁止中斷(IRQ和FIQ烧给,uboot啟動是一個完整的過程,沒有必要也不能被打斷)
??CPU必須為SVC模式(為什么呢喝噪?主要是像異常模式础嫡、用戶模式都不合適。具體深入的原因自己可以查下資料)酝惧。
??(3) Cache和MMU的設(shè)置
??MMU必須關(guān)閉榴鼎。
??指令 Cache可以打開也可以關(guān)閉。
??數(shù)據(jù) Cache必須關(guān)閉晚唇。
為內(nèi)核設(shè)置啟動參數(shù)
??Bootloader與內(nèi)核的交互是單向的巫财, Bootloader將各類參數(shù)傳給內(nèi)核。由于它們不能同時行哩陕,傳遞辦法只有一個:Bootloader將參數(shù)放在某個約定的地方之后平项,再啟動內(nèi)核,內(nèi)核啟動后從這個地方獲得參數(shù)悍及。
??除了約定好參數(shù)存放的地址外闽瓢,還要規(guī)定參數(shù)的結(jié)構(gòu)。Linu2.4x以后的內(nèi)核都期望以標記列表( tagged_list)的形式來傳遞啟動參數(shù)心赶。標記扣讼,就是一種數(shù)據(jù)結(jié)構(gòu);標記列表缨叫,就是挨著存放的多個標記椭符。標記列表以標記 ATAG CORE
開始,以標記 ATAG NONE
結(jié)束耻姥。
??標記的數(shù)據(jù)結(jié)構(gòu)為tag销钝,它由一個 tag_header結(jié)構(gòu)和一個聯(lián)合(union)組成。 tag_ header結(jié)構(gòu)表小標記的類型及長度琐簇,比如是表示內(nèi)存還是表示命令行參數(shù)等蒸健。對于不同類型的標記使用不同的聯(lián)合(union),比如表示內(nèi)存時使用 tag_mem32,表示命令行時使用 tag_cmdline纵装。
??bootloader與內(nèi)核約定的參數(shù)地址征讲,設(shè)置內(nèi)存的起始地址和大小,指定根文件系統(tǒng)在那個分區(qū)橡娄,系統(tǒng)啟動后執(zhí)行的第一個程序linuxrc诗箍,控制臺ttySAC0等。
調(diào)用內(nèi)核
??調(diào)用內(nèi)核就是uboot啟動的最后一步了挽唉。到這里就uboot就完成了他的使命滤祖。
uboot啟動內(nèi)核詳解
??下面我們來展開說下uboot具體是如何調(diào)用內(nèi)核的,引導內(nèi)核啟動的瓶籽。
uboot與Linux內(nèi)核之間的參數(shù)傳遞
??我們知道匠童,uboot啟動后已經(jīng)完成了基本的硬件初始化(如:內(nèi)存、串口等)塑顺,接下來它的主要任務就是加載Linux內(nèi)核到開發(fā)板的內(nèi)存汤求,然后跳轉(zhuǎn)到Linux內(nèi)核所在的地址運行。
??具體是如何跳轉(zhuǎn)呢严拒?做法很簡單扬绪,直接修改PC寄存器的值為Linux內(nèi)核所在的地址,這樣CPU就會從Linux內(nèi)核所在的地址去取指令裤唠,從而執(zhí)行內(nèi)核代碼挤牛。
??在前面我們已經(jīng)知道,在跳轉(zhuǎn)到內(nèi)核以前种蘸,uboot需要做好以下三件事情:
??(1) CPU寄存器的設(shè)置
??R0=0墓赴。
??R1=機器類型ID;對于ARM結(jié)構(gòu)的CPU航瞭,其機器類型ID可以參見 linux/arch/arm tools/ mach-types
??R2=啟動參數(shù)標記列表在RAM中起始基地址诫硕。
??(2) CPU工作模式
??必須禁止中斷(IRQs和FIQs)
??CPU必須為SVC模式
??(3) Cache和MMU的設(shè)置
??MMU必須關(guān)閉
??指令 Cache可以打開也可以關(guān)閉
??數(shù)據(jù) Cache必須關(guān)閉
??其中上面第一步CPU寄存器的設(shè)置中,就是通過R0,R1,R2三個參數(shù)給內(nèi)核傳遞參數(shù)的沧奴。(ATPCS規(guī)則可以參考)
為什么要給內(nèi)核傳遞參數(shù)呢痘括?
??在此之前,uboot已經(jīng)完成了硬件的初始化滔吠,可以說已經(jīng)”適應了“這塊開發(fā)板。然而挠日,內(nèi)核并不是對于所有的開發(fā)板都能完美適配的(如果適配了疮绷,可想而知這個內(nèi)核有多龐大,又或者有新技術(shù)發(fā)明了嚣潜,可以完美的適配各種開發(fā)板)冬骚,此時對于開發(fā)板的環(huán)境一無所知。所以,要想啟動Linux內(nèi)核只冻,uboot必須要給內(nèi)核傳遞一些必要的信息來告訴內(nèi)核當前所處的環(huán)境庇麦。
如何給內(nèi)核傳遞參數(shù)?
??因此喜德,uboot就把機器ID通過R1傳遞給內(nèi)核山橄,Linux內(nèi)核運行的時候首先就從R1中讀取機器ID來判斷是否支持當前機器。這個機器ID實際上就是開發(fā)板CPU的ID舍悯,每個廠家生產(chǎn)出一款CPU的時候都會給它指定一個唯一的ID航棱,大家可以到uboot源碼的arch\arm\include\asm\mach-type.h文件中去查看。
??R2存放的是塊內(nèi)存的基地址萌衬,這塊內(nèi)存中存放的是uboot給Linux內(nèi)核的其他參數(shù)饮醇。這些參數(shù)有內(nèi)存的起始地址、內(nèi)存大小秕豫、Linux內(nèi)核啟動后掛載文件系統(tǒng)的方式等信息朴艰。很明顯,參數(shù)有多個混移,不同的參數(shù)有不同的內(nèi)容呵晚,為了讓Linux內(nèi)核能精確的解析出這些參數(shù),雙方在傳遞參數(shù)的時候要求參數(shù)在存放的時猴需要按照雙方規(guī)定的格式存放沫屡。
??除了約定好參數(shù)存放的地址外饵隙,還要規(guī)定參數(shù)的結(jié)構(gòu)。Linux2.4.x以后的內(nèi)核都期望以標記列表(tagged_list)的形式來傳遞啟動參數(shù)沮脖。標記金矛,就是一種數(shù)據(jù)結(jié)構(gòu);標記列表勺届,就是挨著存放的多個標記驶俊。標記列表以標記ATAG_CORE
開始,以標記ATAG_NONE
結(jié)束免姿。
??標記的數(shù)據(jù)結(jié)構(gòu)為tag饼酿,它由一個tag_header結(jié)構(gòu)和一個聯(lián)合(union)組成。tag_header結(jié)構(gòu)表示標記的類型及長度胚膊,比如是表示內(nèi)存還是表示命令行參數(shù)等故俐。對于不同類型的標記使用不同的聯(lián)合(union),比如表示內(nèi)存時使用tag_ mem32紊婉,表示命令行時使用 tag_cmdline药版。具體代碼見arch\arm\include\asm\setup.h。
??從上面可以看出喻犁,struct_tag結(jié)構(gòu)體由structtag_header+聯(lián)合體union構(gòu)成槽片,結(jié)構(gòu)體struct tag_header用來描述每個tag的頭部信息何缓,如tag的類型,tag大小还栓。聯(lián)合體union用來描述每個傳遞給Linux內(nèi)核的參數(shù)信息碌廓。
??下面以傳遞內(nèi)存標記、傳遞命令行參數(shù)為例來說明參數(shù)的傳遞剩盒。
??(1)設(shè)置開始標記ATAG_CORE
tag->hdr.tag = ATAG_CORE;
tag->hdr.size = tag_size(tag_core);
tag->u.core.flags = params->u1.s.flags & FLAG_READONLY;
tag->u.core.pagesize = params->u1.s.page_size;
tag->u.core.rootdev = params->u1.s.rootdev;
tag = tag_next(tag);
??涉及到的結(jié)構(gòu)體定義如下
struct tag_header {
__u32 size;
__u32 tag;
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
__u32 flags; /* bit 0 = read-only */
__u32 pagesize;
__u32 rootdev;
};
??其中tag_next谷婆,tag_size定義如下,指向當前標記的結(jié)尾
版權(quán)聲明:本文為博主原創(chuàng)文章勃刨,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議波材,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
??(2)設(shè)置內(nèi)存標記
t->hdr.tag = ATAG_MEM;
t->hdr.size = tag_size(tag_mem32);
t->u.mem.start = CFG_GLOBAL_RAM_BASE;
t->u.mem.size = CFG_GLOBAL_RAM_SIZE;
t = tag_next(t);
??相關(guān)結(jié)構(gòu)體定義如下
#define ATAG_MEM 0x54410002
struct tag_mem32 {
__u32 size;
__u32 start; /* physical start address */
};
??(3)設(shè)置命令行參數(shù)標記
??命令行參數(shù)是一個字符串身隐,一般用它來告訴內(nèi)核掛載根文件系統(tǒng)的方式廷区。由uboot的bootargs環(huán)境變量提供,它的內(nèi)容有如下兩種格式
root=nfs nfsroot=202.193.61.237:/work/nfs_root/first_fs ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
root=/dev/mtdblock2 ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200
名稱 | 含義 |
---|---|
root | 告訴Linux內(nèi)核掛載根文件系統(tǒng)的方式贾铝,nfs表示以NFS服務的方式掛載根文件系統(tǒng)隙轻,/dev/mtdblock2表示根文件系統(tǒng)在MTD設(shè)置的第二個分區(qū)上。 |
nfsroot | 告訴Linux內(nèi)核垢揩,以NFS方式掛載根文件系統(tǒng)時玖绿,根文件系統(tǒng)所在主機的P地址和路徑 |
ip | 告訴Linux內(nèi)核,啟動后它的p地址 |
init | 告訴Linux內(nèi)核叁巨,啟動的第一個應用程序是根目錄下的linuxrc程序 |
console | 告訴Linux區(qū)內(nèi)核斑匪,控制臺為ttySAC0,波特率為115200 |
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (strlen(params->commandline) + 3 +
sizeof(struct tag_header)) >> 2;
strcpy(tag->u.cmdline.cmdline, params->commandline);
tag = tag_next(tag);
??相關(guān)結(jié)構(gòu)體定義如下
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410003
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
??(4)設(shè)置結(jié)束標記
tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;
??我們明白了運行Linux區(qū)內(nèi)核的時候,uboot需要給內(nèi)核的傳遞的參數(shù)锋勺,接下來我們就來看看如何從uboot中跳到Linux內(nèi)核蚀瘸。
uboot跳轉(zhuǎn)到Linux內(nèi)核
??在uboot中可以使用go和bootm來跳轉(zhuǎn)到內(nèi)核,這兩個命令的區(qū)別如下:
??(1) go命令僅僅修改pc的值到指定地址
??格式:go addr
??(2) bootm命令是uboot專門用來啟動uImage格式的Linux內(nèi)核庶橱,它在修改pc的值到指定地址之前贮勃,會設(shè)置傳遞給Linux內(nèi)核的參數(shù),用法如下:
??格式:bootm addr
版權(quán)聲明:本文為博主原創(chuàng)文章苏章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議寂嘉,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510
uboot中bootm命令實現(xiàn)
??bootm命令在uboot源碼common/cmd_bootm.c中實現(xiàn)枫绅,它的功能如下:
??(1)讀取uImage頭部泉孩,把內(nèi)核拷貝到合適的地方。
??(2)把參數(shù)給內(nèi)核準備好撑瞧。
??(3)引導內(nèi)核棵譬。
??當我們使用我們在uboot使用bootm命令后,bootm命令會從uImage頭中讀取信息后预伺,發(fā)現(xiàn)是Linux內(nèi)核订咸,就會調(diào)用do_bootm_linux()函數(shù),函數(shù)的具體實現(xiàn)bootm.c中
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & BOOTM_STATE_OS_GO) {
boot_jump_linux(images);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images);
return 0;
}
??do_bootm_linux 函數(shù)最終會 跳轉(zhuǎn)執(zhí)行 boot_prep_linux 和 boot_jump_linux 函數(shù)酬诀,首先分析 boot_prep_linux 函數(shù)(位于 bootm.c 文件中):
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("bootargs"); //從環(huán)境變量中獲取 bootargs 的值
脏嚷。。瞒御。父叙。。肴裙。趾唱。
setup_board_tags(¶ms);
setup_end_tag(gd->bd); //將 tag 參數(shù)保存在指定位置
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
do_nonsec_virt_switch();
}
?? 從代碼可以看出來,boot_prep_linux蜻懦,主要功能是將 tag 參數(shù)保存到指定位置甜癞,比如 bootargs 環(huán)境變量 tag,串口 tag宛乃,接下來分析 boot_jump_linux 函數(shù)(位于 bootm.c 文件中):
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = gd->bd->bi_arch_number; //獲取機器id (在 board/samsung/jz2440/jz2440.c 中設(shè)置悠咱,為 MACH_TYPE_SMDK2410(193))
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep; //獲取 kernel的入口地址,此處應為 30000000
s = getenv("machid"); //從環(huán)境變量里獲取機器id (本例中還未在環(huán)境變量里設(shè)置過機器 id)
if (s) { //判斷環(huán)境變量里是否設(shè)置機器id
strict_strtoul(s, 16, &machid); //如果設(shè)置則用環(huán)境變量里的機器id
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params; //獲取 tag參數(shù)地址征炼,gd->bd->bi_boot_params在 setup_start_tag 函數(shù)里設(shè)置
if (!fake) kernel_entry(0, machid, r2); } //進入內(nèi)核
??通過分析可以看出析既,最終進入內(nèi)核的函數(shù)為 :
kernel_entry(0, machid, r2)
??到這里bootm就成功給內(nèi)核傳遞了參數(shù),并跳轉(zhuǎn)到了內(nèi)核谆奥。關(guān)于go命令的實現(xiàn)可以自己參考內(nèi)核眼坏,在cmd_boot.c文件中,所不同的是酸些,go命令實現(xiàn)的時候沒有設(shè)置參數(shù)宰译,只是簡單的跳轉(zhuǎn)執(zhí)行。如果想要使用go來跳轉(zhuǎn)到Linux內(nèi)核擂仍,我們需要做簡單的修改囤屹,有興趣的可以自己研究下,這里就不展開講了逢渔。
??至此肋坚,uboot就啟動了內(nèi)核。啟動內(nèi)核后就是掛載根文件系統(tǒng)了肃廓,下篇將具體介紹是如何掛載根文件系統(tǒng)的智厌。
??構(gòu)建根文件系統(tǒng)
內(nèi)核鏡像格式vmlinuz和zImage和uImage
??最后插講下內(nèi)核的不同映像格式的區(qū)別:
??(1)uboot經(jīng)過編譯直接生成的elf格式的可執(zhí)行程序是u-boot,這個程序類似于windows下的exe格式盲赊,在操作系統(tǒng)下是可以直接執(zhí)行的铣鹏。但是這種格式不能用來燒錄下載。我們用來燒錄下載的是u-boot.bin哀蘑,這個東西是由u-boot使用arm-linux-objcopy工具進行加工(主要目的是去掉一些無用的)得到的诚卸。這個u-boot.bin就叫鏡像(image)葵第,鏡像就是用來燒錄到iNand中執(zhí)行的。
??(2)linux內(nèi)核經(jīng)過編譯后也會生成一個elf格式的可執(zhí)行程序合溺,叫vmlinux或vmlinuz卒密,這個就是原始的未經(jīng)任何處理加工的原版內(nèi)核elf文件;嵌入式系統(tǒng)部署時燒錄的一般不是這個vmlinuz/vmlinux棠赛,而是要用objcopy工具去制作成燒錄鏡像格式(就是u-boot.bin這種哮奇,但是內(nèi)核沒有.bin后綴),經(jīng)過制作加工成燒錄鏡像的文件就叫Image(制作把78M大的精簡成了7.5M睛约,因此這個制作燒錄鏡像主要目的就是縮減大小鼎俘,節(jié)省磁盤)。
??(3)原則上Image就可以直接被燒錄到Flash上進行啟動執(zhí)行(類似于u-boot.bin)辩涝,但是實際上并不是這么簡單贸伐。實際上linux的作者們覺得Image還是太大了所以對Image進行了壓縮,并且在image壓縮后的文件的前端附加了一部分解壓縮代碼膀值。構(gòu)成了一個壓縮格式的鏡像就叫zImage棍丐。(因為當年Image大小剛好比一張軟盤(軟盤有2種,1.2M的和1.44MB兩種)大沧踏,為了節(jié)省1張軟盤的錢于是乎設(shè)計了這種壓縮Image成zImage的技術(shù))歌逢。
??(4)uboot為了啟動linux內(nèi)核,還發(fā)明了一種內(nèi)核格式叫uImage翘狱。uImage是由zImage加工得到的秘案,uboot中有一個工具,可以將zImage加工生成uImage潦匈。注意:uImage不關(guān)linux內(nèi)核的事阱高,linux內(nèi)核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage來給uboot啟動茬缩。這個加工過程其實就是在zImage前面加上64字節(jié)的uImage的頭信息即可赤惊。
??(5)原則上uboot啟動時應該給他uImage格式的內(nèi)核鏡像,但是實際上uboot中也可以支持zImage凰锡,是否支持就看x210_sd.h中是否定義了LINUX_ZIMAGE_MAGIC這個宏未舟。所以大家可以看出:有些uboot是支持zImage啟動的,有些則不支持掂为。但是所有的uboot肯定都支持uImage啟動裕膀。
??(6)如果直接在kernel底下去make uImage會提供mkimage command not found。解決方案是去uboot/tools下cp mkimage /usr/local/bin/勇哗,復制mkimage工具到系統(tǒng)目錄下昼扛。再去make uImage即可。
??通過上面的介紹我們了解了內(nèi)核鏡像的各種格式欲诺,如果通過uboot啟動內(nèi)核抄谐,Linux必須為uImage格式渺鹦。
??大家的鼓勵是我繼續(xù)創(chuàng)作的動力,如果覺得寫的不錯斯稳,歡迎關(guān)注海铆,點贊迹恐,收藏挣惰,轉(zhuǎn)發(fā),謝謝殴边!
版權(quán)聲明:本文為博主原創(chuàng)文章憎茂,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明锤岸。
本文鏈接:https://blog.csdn.net/qq_16933601/article/details/106244510
本文參考:<嵌入式Linux應用開發(fā)完全手冊>
如遇到排版錯亂的問題竖幔,可以通過以下鏈接訪問我的CSDN。
CSDN:CSDN搜索“嵌入式與Linux那些事