深入MTK平台bootloader启动分析笔记
bootloader到kernel啟動總邏輯流程圖
ARM架構(gòu)中,EL0/EL1是必須實現(xiàn),EL2/EL3是選配,ELx跟層級對應(yīng)關(guān)系:
EL0 -- app
EL1 -- Linux kernel 、lk
EL2 -- hypervisor(虛擬化)
EL3 -- ARM trust firmware 、pre-loader
若平臺未實現(xiàn)EL3(atf),pre-loader直接加載lk:
若平臺實現(xiàn)EL3,則需要先加載完ATF再由ATF去加載lk:
bootloader 啟動分兩個階段,一個是pre-loader加載lk(u-boot)階段,另一個是lk加載kernel階段。
下面跟著流程圖簡述第一個階段的加載流程。
1-3:設(shè)備上電起來后,跳轉(zhuǎn)到Boot ROM(不是flash)中的boot code中執(zhí)行把pre-loader加載起到ISRAM, 因為當(dāng)前DRAM(RAM分SRAM跟DRAM,簡單來說SRAM就是cache,DRAM就是普通內(nèi)存)還沒有準(zhǔn)備好,所以要先把pre-loader load到芯片內(nèi)部的ISRAM(Internal SRAM)中。
4-6:pre-loader初始化好DRAM后就將lk從flash(nand/emmc)中加載到DRAM中運行;
7-8:解壓bootimage成ramdisk跟kernel并載入DRAM中,初始化dtb;
9-11:lk跳轉(zhuǎn)到kernl初始化, kernel初始化完成后fork出init進(jìn)程, 然后拉起ramdisk中的init程序,進(jìn)入用戶空間初始化,init進(jìn)程fork出zygote進(jìn)程..直到整個Android啟動完成.
-- 從pre-loader到lk(mt6580為例)
Pre-loader主要干的事情就是初始化某些硬件,比如:UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,建立起最基本的運行環(huán)境,最重要的就是初始化DRAM。
源碼流程如下:
./bootloader/preloader/platform/mt6580/src/init/init.s .p?.text.start ....globl?_start .../*?set?the?cpu?to?SVC32?mode?*/MRS?r0,cpsrBIC?r0,r0,#0x1fORR?r0,r0,#0xd3MSR?cpsr,r0/*?disable?interrupt?*/MRS?r0,?cpsrMOV?r1,?#INT_BITORR?r0,?r0,?r1MSR?cpsr_cxsf,?r0... setup_stk?:/*?setup?stack?*/LDR?r0,?stackLDR?r1,?stacksz ...entry?:LDR?r0,?=bldr_args_addr/*?跳轉(zhuǎn)到C代碼?main?入口?*/B???maininit.s 主要干的事情是切換系統(tǒng)到管理模式(svc)(如果平臺有實現(xiàn)el3,那么pre-loader運行在el3,否則運行在el1),禁止irq/fiq,設(shè)置stack等, 然后jump到c代碼main函數(shù)入口。?
進(jìn)入源碼分析
./bootloader/preloader/platform/mt6580/src/core/main.cvoid?main(u32?*arg) {struct?bldr_command_handler?handler;u32?jump_addr,?jump_arg;/*?get?the?bldr?argument?*/bldr_param?=?(bl_param_t?*)*arg;//?初始化uart?mtk_uart_init(UART_SRC_CLK_FRQ,?CFG_LOG_BAUDRATE);//?這里干了很多事情,包括各種的平臺硬件(timer,pmic,gpio,wdt...)初始化工作.bldr_pre_process();handler.priv?=?NULL;handler.attr?=?0;handler.cb???=?bldr_cmd_handler;//?這里是獲取啟動模式等信息保存到全局變量g_boot_mode和g_meta_com_type?中.BOOTING_TIME_PROFILING_LOG("before?bldr_handshake");bldr_handshake(&handler);BOOTING_TIME_PROFILING_LOG("bldr_handshake");//?下面跟?secro?img?相關(guān),跟平臺設(shè)計強相關(guān)./*?security?check?*/sec_lib_read_secro();sec_boot_check();device_APC_dom_setup();BOOTING_TIME_PROFILING_LOG("sec_boot_check");/*?如果已經(jīng)實現(xiàn)EL3,那么進(jìn)行tz預(yù)初始化?*/ #if?CFG_ATF_SUPPORTtrustzone_pre_init(); #endif/*?bldr_load_images 此函數(shù)要做的事情就是把lk從ROM中指定位置load到DRAM中,開機log中可以看到具體信息: [PART]?load?"lk"?from?0x0000000001CC0200?(dev)?to?0x81E00000?(mem)?[SUCCESS] 這里準(zhǔn)備好了jump到DRAM的具體地址,下面詳細(xì)分析. */if?(0?!=?bldr_load_images(&jump_addr))?{print("%s?Second?Bootloader?Load?Failed\n",?MOD);goto?error;}/*? 該函數(shù)的實現(xiàn)體是platform_post_init,這里要干的事情其實比較簡單,就是通過 hw_check_battery去判斷當(dāng)前系統(tǒng)是否存在電池(判斷是否有電池ntc腳來區(qū)分), 如果不存在就陷入while(1)卡住了,所以在es階段調(diào)試有時候 需要接電源調(diào)試的,就需要改這里面的邏輯才可正常開機? */bldr_post_process();//?atf?正式初始化,使用特有的系統(tǒng)調(diào)用方式實現(xiàn). #if?CFG_ATF_SUPPORTtrustzone_post_init(); #endif/*?跳轉(zhuǎn)傳入lk的參數(shù),包括boot?time/mode/reason?等,這些參數(shù)在platform_set_boot_args 函數(shù)獲取。 */jump_arg?=?(u32)&(g_dram_buf->boottag);/*?執(zhí)行jump系統(tǒng)調(diào)用,從?pre-loader?跳轉(zhuǎn)到?lk執(zhí)行,如果實現(xiàn)了EL3情況就要復(fù)雜一些,需要先跳轉(zhuǎn)到EL3初始化,然后再跳回lk,pre-loader執(zhí)行在EL3,LK執(zhí)行在EL1)從log可以類似看到這些信息:[BLDR]?jump?to?0x81E00000[BLDR]?<0x81E00000>=0xEA000007[BLDR]?<0x81E00004>=0xEA0056E2 */#if?CFG_ATF_SUPPORT/*?64S3,32S1,32S1?(MTK_ATF_BOOT_OPTION?=?0)*?re-loader?jump?to?LK?directly?and?then?LK?jump?to?kernel?directly?*/if?(?BOOT_OPT_64S3?==?g_smc_boot_opt?&&BOOT_OPT_32S1?==?g_lk_boot_opt?&&BOOT_OPT_32S1?==?g_kernel_boot_opt)?{print("%s?64S3,32S1,32S1,?jump?to?LK\n",?MOD);bldr_jump(jump_addr,?jump_arg,?sizeof(boot_arg_t));}?else?{//?如果?el3?使用aarch64實現(xiàn),則jump到atf.print("%s?Others,?jump?to?ATF\n",?MOD);bldr_jump64(jump_addr,?jump_arg,?sizeof(boot_arg_t));} #elsebldr_jump(jump_addr,?jump_arg,?sizeof(boot_arg_t)); #endif//?如果沒有取到j(luò)ump_addr,則打印錯誤提示,進(jìn)入while(1)等待. error:platform_error_handler(); }main 函數(shù)小結(jié):
1、各種硬件初始化(uart、pmic、wdt、timer、mem..);
2、獲取系統(tǒng)啟動模式等,保存在全局變量中;
3、Security check,跟secro.img相關(guān);
4、如果系統(tǒng)已經(jīng)實現(xiàn)el3,則進(jìn)入tz初始化;
5、獲取lk加載到DRAM的地址(固定值),然后從ROM中找到lk分區(qū)的地址, 如果沒找到j(luò)ump_addr,則 goto error;
6、battery check,如果沒有電池就會陷入while(1);
7、jump到lk(如果有實現(xiàn)el3,則會先jump到el3,然后再回到lk)
重點函數(shù)分析
bldr_load_images
函數(shù)主要干的事情就是找到lk分區(qū)地址和lk加載到DRAM中的地址, 準(zhǔn)備好jump到lk執(zhí)行。
如下源碼分析:
static?int?bldr_load_images(u32?*jump_addr) {int?ret?=?0;blkdev_t?*bootdev;u32?addr?=?0;char?*name;u32?size?=?0;u32?spare0?=?0;u32?spare1?=?0;... /*?這個地址是一個固定值,可以查到定義在:./bootloader/preloader/platform/mt6580/default.mak:95:CFG_UBOOT_MEMADDR?:=?0x81E00000從log中可以看到:[BLDR]?jump?to?0x81E00000 */addr?=?CFG_UBOOT_MEMADDR;/*?然后去ROM找到lk所在分區(qū)地址?*/ret?=?bldr_load_part("lk",?bootdev,?&addr,?&size);if?(ret)return?ret;*jump_addr?=?addr;}//?這個函數(shù)邏輯很簡單,就不需要多說了. int?bldr_load_part(char?*name,?blkdev_t?*bdev,?u32?*addr,?u32?*size) {part_t?*part?=?part_get(name);if?(NULL?==?part)?{print("%s?%s?partition?not?found\n",?MOD,?name);return?-1;}return?part_load(bdev,?part,?addr,?0,?size); }//?真正的load實現(xiàn)是在part_load函數(shù). int?part_load(blkdev_t?*bdev,?part_t?*part,?u32?*addr,?u32?offset,?u32?*size) {int?ret;img_hdr_t?*hdr?=?(img_hdr_t?*)img_hdr_buf;part_hdr_t?*part_hdr?=?&hdr->part_hdr;gfh_file_info_t?*file_info_hdr?=?&hdr->file_info_hdr;/*?specify?the?read?offset?*/u64?src?=?part->startblk?*?bdev->blksz?+?offset;u32?dsize?=?0,?maddr?=?0;u32?ms;//?檢索分區(qū)頭是否正確。/*?retrieve?partition?header.?*/if?(blkdev_read(bdev,?src,?sizeof(img_hdr_t),?(u8*)hdr,0)?!=?0)?{print("[%s]bdev(%d)?read?error?(%s)\n",?MOD,?bdev->type,?part->name);return?-1;}if?(part_hdr->info.magic?==?PART_MAGIC)?{/*?load?image?with?partition?header?*/part_hdr->info.name[31]?=?'\0';/*輸出分區(qū)的各種信息,從log中可以看到:[PART]?Image?with?part?header[PART]?name?:?lk[PART]?addr?:?FFFFFFFFh?mode?:?-1[PART]?size?:?337116[PART]?magic:?58881688h*/print("[%s]Img?with?part?header\n",?MOD);print("[%s]name:%s\n",?MOD,?part_hdr->info.name);print("[%s]addr:%xh\n",?MOD,?part_hdr->info.maddr);print("[%s]size:%d\n",?MOD,?part_hdr->info.dsize);print("[%s]magic:%xh\n",?MOD,?part_hdr->info.magic);maddr?=?part_hdr->info.maddr;dsize?=?part_hdr->info.dsize;src?+=?sizeof(part_hdr_t);memcpy(part_info?+?part_num,?part_hdr,?sizeof(part_hdr_t));part_num++;}?else?{print("[%s]%s?img?not?exist\n",?MOD,?part->name);return?-1;}//?如果maddr沒有定義,那么就使用前面?zhèn)魅氲牡刂穉ddr.if?(maddr?==?PART_HEADER_MEMADDR/*0xffffffff*/)maddr?=?*addr;if_overlap_with_dram_buffer((u32)maddr,?((u32)maddr?+?dsize));ms?=?get_timer(0);if?(0?==?(ret?=?blkdev_read(bdev,?src,?dsize,?(u8*)maddr,0)))*addr?=?maddr;ms?=?get_timer(ms);/*?如果一切順利就會打印出關(guān)鍵信息:[PART]?load?"lk"?from?0x0000000001CC0200?(dev)?to?0x81E00000?(mem)?[SUCCESS][PART]?load?speed:?25324KB/s,?337116?bytes,?13ms */print("\n[%s]load?\"%s\"?from?0x%llx(dev)?to?0x%x?(mem)?[%s]\n",?MOD,part->name,?src,?maddr,?(ret?==?0)???"SUCCESS"?:?"FAILED");if(?ms?==?0?)ms+=1;print("[%s]load?speed:%dKB/s,%d?bytes,%dms\n",?MOD,?((dsize?/?ms)?*?1000)?/?1024,?dsize,?ms);return?ret; }bldr_post_process
函數(shù)主要干的事情就是從pmic去檢查是否有電池存在,如果沒有就等待
如下源碼分析,比較簡單:
//?就是包了一層而已. static?void?bldr_post_process(void) {platform_post_init(); }//?重點是這個函數(shù): void?platform_post_init(void) {/*?normal?boot?to?check?battery?exists?or?not?*/if?(g_boot_mode?==?NORMAL_BOOT?&&?!hw_check_battery()?&&?usb_accessory_in())?{ ...pl_charging(1);do?{mdelay(300);/*?檢查電池是否存在,?如果使用電源調(diào)試則需要修改此函數(shù)邏輯?*/if?(hw_check_battery())break;/*?喂狗,以免超時被狗咬?*/platform_wdt_all_kick();}?while(1);/*?disable?force?charging?mode?*/pl_charging(0);}... }Pre-loader 到 Lk的源碼分析到這就完成了.
推薦閱讀:
? ??專輯|Linux文章匯總
? ??專輯|程序人生
? ??專輯|C語言
嵌入式Linux
微信掃描二維碼,關(guān)注我的公眾號?
總結(jié)
以上是生活随笔為你收集整理的深入MTK平台bootloader启动分析笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 墨卡托投影和高斯-克吕格 (Gauss-
- 下一篇: 二逼了吧,你竟然在中断里面休眠