深入MTK平台bootloader启动分析笔记
bootloader到kernel啟動總邏輯流程圖
ARM架構中,EL0/EL1是必須實現,EL2/EL3是選配,ELx跟層級對應關系:
EL0 -- app
EL1 -- Linux kernel 、lk
EL2 -- hypervisor(虛擬化)
EL3 -- ARM trust firmware 、pre-loader
若平臺未實現EL3(atf),pre-loader直接加載lk:
若平臺實現EL3,則需要先加載完ATF再由ATF去加載lk:
bootloader 啟動分兩個階段,一個是pre-loader加載lk(u-boot)階段,另一個是lk加載kernel階段。
下面跟著流程圖簡述第一個階段的加載流程。
1-3:設備上電起來后,跳轉到Boot ROM(不是flash)中的boot code中執行把pre-loader加載起到ISRAM, 因為當前DRAM(RAM分SRAM跟DRAM,簡單來說SRAM就是cache,DRAM就是普通內存)還沒有準備好,所以要先把pre-loader load到芯片內部的ISRAM(Internal SRAM)中。
4-6:pre-loader初始化好DRAM后就將lk從flash(nand/emmc)中加載到DRAM中運行;
7-8:解壓bootimage成ramdisk跟kernel并載入DRAM中,初始化dtb;
9-11:lk跳轉到kernl初始化, kernel初始化完成后fork出init進程, 然后拉起ramdisk中的init程序,進入用戶空間初始化,init進程fork出zygote進程..直到整個Android啟動完成.
-- 從pre-loader到lk(mt6580為例)
Pre-loader主要干的事情就是初始化某些硬件,比如:UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,建立起最基本的運行環境,最重要的就是初始化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/*?跳轉到C代碼?main?入口?*/B???maininit.s 主要干的事情是切換系統到管理模式(svc)(如果平臺有實現el3,那么pre-loader運行在el3,否則運行在el1),禁止irq/fiq,設置stack等, 然后jump到c代碼main函數入口。?
進入源碼分析
./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?相關,跟平臺設計強相關./*?security?check?*/sec_lib_read_secro();sec_boot_check();device_APC_dom_setup();BOOTING_TIME_PROFILING_LOG("sec_boot_check");/*?如果已經實現EL3,那么進行tz預初始化?*/ #if?CFG_ATF_SUPPORTtrustzone_pre_init(); #endif/*?bldr_load_images 此函數要做的事情就是把lk從ROM中指定位置load到DRAM中,開機log中可以看到具體信息: [PART]?load?"lk"?from?0x0000000001CC0200?(dev)?to?0x81E00000?(mem)?[SUCCESS] 這里準備好了jump到DRAM的具體地址,下面詳細分析. */if?(0?!=?bldr_load_images(&jump_addr))?{print("%s?Second?Bootloader?Load?Failed\n",?MOD);goto?error;}/*? 該函數的實現體是platform_post_init,這里要干的事情其實比較簡單,就是通過 hw_check_battery去判斷當前系統是否存在電池(判斷是否有電池ntc腳來區分), 如果不存在就陷入while(1)卡住了,所以在es階段調試有時候 需要接電源調試的,就需要改這里面的邏輯才可正常開機? */bldr_post_process();//?atf?正式初始化,使用特有的系統調用方式實現. #if?CFG_ATF_SUPPORTtrustzone_post_init(); #endif/*?跳轉傳入lk的參數,包括boot?time/mode/reason?等,這些參數在platform_set_boot_args 函數獲取。 */jump_arg?=?(u32)&(g_dram_buf->boottag);/*?執行jump系統調用,從?pre-loader?跳轉到?lk執行,如果實現了EL3情況就要復雜一些,需要先跳轉到EL3初始化,然后再跳回lk,pre-loader執行在EL3,LK執行在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實現,則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//?如果沒有取到jump_addr,則打印錯誤提示,進入while(1)等待. error:platform_error_handler(); }main 函數小結:
1、各種硬件初始化(uart、pmic、wdt、timer、mem..);
2、獲取系統啟動模式等,保存在全局變量中;
3、Security check,跟secro.img相關;
4、如果系統已經實現el3,則進入tz初始化;
5、獲取lk加載到DRAM的地址(固定值),然后從ROM中找到lk分區的地址, 如果沒找到jump_addr,則 goto error;
6、battery check,如果沒有電池就會陷入while(1);
7、jump到lk(如果有實現el3,則會先jump到el3,然后再回到lk)
重點函數分析
bldr_load_images
函數主要干的事情就是找到lk分區地址和lk加載到DRAM中的地址, 準備好jump到lk執行。
如下源碼分析:
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所在分區地址?*/ret?=?bldr_load_part("lk",?bootdev,?&addr,?&size);if?(ret)return?ret;*jump_addr?=?addr;}//?這個函數邏輯很簡單,就不需要多說了. 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實現是在part_load函數. 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;//?檢索分區頭是否正確。/*?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';/*輸出分區的各種信息,從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沒有定義,那么就使用前面傳入的地址addr.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);/*?如果一切順利就會打印出關鍵信息:[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
函數主要干的事情就是從pmic去檢查是否有電池存在,如果沒有就等待
如下源碼分析,比較簡單:
//?就是包了一層而已. static?void?bldr_post_process(void) {platform_post_init(); }//?重點是這個函數: 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);/*?檢查電池是否存在,?如果使用電源調試則需要修改此函數邏輯?*/if?(hw_check_battery())break;/*?喂狗,以免超時被狗咬?*/platform_wdt_all_kick();}?while(1);/*?disable?force?charging?mode?*/pl_charging(0);}... }Pre-loader 到 Lk的源碼分析到這就完成了.
推薦閱讀:
? ??專輯|Linux文章匯總
? ??專輯|程序人生
? ??專輯|C語言
嵌入式Linux
微信掃描二維碼,關注我的公眾號?
總結
以上是生活随笔為你收集整理的深入MTK平台bootloader启动分析笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 墨卡托投影和高斯-克吕格 (Gauss-
- 下一篇: 二逼了吧,你竟然在中断里面休眠