MTK平臺修改Bootloader源代碼,讓Android設備一通電就自動開機
爲什麼要一通電就自動開機
總所周知,一臺Android設備,默認情況下,插上USB供電,都是顯示正在充電的電池圖標,按住開機鍵才能開機。如果你手裏只有一臺手機,按住開機鍵去開機很容易,如果你手裏有1000臺手機呢?通常見於微信推廣、微信站街、手遊工作室、刷單工作室等,往往擁有數百臺數千臺手機,使用羣控和腳本批量控制這些手機做一些interesting的事情。這些工作室往往使用定製的手機,手裏握着全套Android源代碼,這樣可以方便的做很多root都做不到的事情。那麼一個工作室如果有數百臺手機,如果斷電了,需要開機,或者需要重啓,都是很痛苦的事情,如果能讓這些Android手機一通電就自動開機,豈不是方便極了。那麼在有全套Android源代碼的情況下,如何分析代碼並修改去實現這一功能呢?
思路
其實一通電就開機這個功能,和Android沒什麼關係,分析的時候,也不要再去看Android源代碼了。首先了解一下Android設備的啓動流程:
實際上通電以後,屏幕上出現電池充電的圖標,就一直停留在Bootloader這個步驟了,電池充電和屏幕顯示充電的動畫都是在這裏面實現的,這個時候連Linux內核都還沒加載,更不用說Android了。
所以要實現這個功能,首先鎖定代碼位置,那就是Android的Bootloader,通常是LK,代碼位於bootable/bootloader/lk/。鎖定了範圍,只需要分析這裏面的代碼就行了。LK的代碼量並不多,只有幾千行,藉助開發板使用串口打LOG調試,觀察LOG輸出內容,並以此爲關鍵詞在代碼中搜索,定位LOG對應的代碼,相信你很快就能找到方法。這裏有網上找到的兩篇文章,以MTK平臺爲例,分析了按下開機鍵後,由Preloader -> LK -> Kernel 的過程,結合文章中的時序圖去閱讀LK代碼,會得到事半功倍的效果,相信你很快就能分析出來,充電的時候,代碼執行到那裏,按住開機鍵的時候,代碼執行到了哪裏。
深入MTK平臺bootloader啓動之【 Pre-loader -> Lk】分析筆記
深入MTK平臺bootloader啓動之【 lk -> kernel】分析筆記
MTK平臺改法
有了思路以後,使用開發板串口打LOG,通電進入到充電界面後,觀察串口輸出的日誌,用日誌內容中的關鍵詞去LK代碼中搜索,進一步定位關鍵代碼位置,通過閱讀代碼,bootable\bootloader\lk\platform\mt6582\boot_mode.c 中的 void boot_mode_select(void) 函數引起了我的注意,看函數名,很可能是通電以後,選擇如何boot,是開機呢,還是充電呢,還是進入recovery。
void boot_mode_select(void)
{
int factory_forbidden = 0;
// int forbid_mode;
/*We put conditions here to filer some cases that can not do key detection*/
extern int kedump_mini(void) __attribute__((weak));
if (kedump_mini)
{
if (kedump_mini())
{
mrdump_check();
return;
}
}
if (meta_detection())
{
return;
}
mrdump_check();
#if defined (HAVE_LK_TEXT_MENU)
/*Check RTC to know if system want to reboot to Fastboot*/
if(Check_RTC_PDN1_bit13())
{
printf("[FASTBOOT] reboot to boot loader\n");
g_boot_mode = FASTBOOT;
Set_Clr_RTC_PDN1_bit13(false);
return;
}
/*If forbidden mode is factory, cacel the factory key detection*/
if(g_boot_arg->sec_limit.magic_num == 0x4C4C4C4C)
{
if(g_boot_arg->sec_limit.forbid_mode == F_FACTORY_MODE)
{
//Forbid to enter factory mode
printf("%s Forbidden\n",MODULE_NAME);
factory_forbidden=1;
}
}
// forbid_mode = g_boot_arg->boot_mode &= 0x000000FF;
/*If boot reason is power key + volumn down, then
disable factory mode dectection*/
if(mtk_detect_pmic_just_rst())
{
factory_forbidden=1;
}
/*Check RTC to know if system want to reboot to Recovery*/
if(Check_RTC_Recovery_Mode())
{
g_boot_mode = RECOVERY_BOOT;
return;
}
/*If MISC Write has not completed in recovery mode
before system reboot, go to recovery mode to
finish remain tasks*/
if(unshield_recovery_detection())
{
return;
}
ulong begin = get_timer(0);
/*we put key dectection here to detect key which is pressed*/
printf("eng build\n");
printf("MT65XX_FACTORY_KEY 0x%x\n",MT65XX_FACTORY_KEY);
printf("MT65XX_BOOT_MENU_KEY 0x%x\n",MT65XX_BOOT_MENU_KEY);
printf("MT65XX_RECOVERY_KEY 0x%x\n",MT65XX_RECOVERY_KEY);
while(get_timer(begin)<50)
{
if(!factory_forbidden){
if(mtk_detect_key(MT65XX_FACTORY_KEY))
{
printf("%s Detect key\n",MODULE_NAME);
printf("%s Enable factory mode\n",MODULE_NAME);
g_boot_mode = FACTORY_BOOT;
//video_printf("%s : detect factory mode !\n",MODULE_NAME);
return;
}
}
if(mtk_detect_key(MT65XX_BOOT_MENU_KEY))
{
printf("\n%s Check boot menu\n",MODULE_NAME);
printf("%s Wait 50ms for special keys\n",MODULE_NAME);
mtk_wdt_disable();
boot_mode_menu_select();
mtk_wdt_init();
return;
}
#ifdef MT65XX_RECOVERY_KEY
if(mtk_detect_key(MT65XX_RECOVERY_KEY))
{
printf("%s Detect cal key\n",MODULE_NAME);
printf("%s Enable recovery mode\n",MODULE_NAME);
g_boot_mode = RECOVERY_BOOT;
//video_printf("%s : detect recovery mode !\n",MODULE_NAME);
return;
}
#endif
}
#else
/*We put conditions here to filer some cases that can not do key detection*/
/*Check RTC to know if system want to reboot to Fastboot*/
#ifdef MTK_FASTBOOT_SUPPORT
if(Check_RTC_PDN1_bit13())
{
dprintf(INFO,"[FASTBOOT] reboot to boot loader\n");
g_boot_mode = FASTBOOT;
Set_Clr_RTC_PDN1_bit13(false);
return ;
}
#endif
/*If forbidden mode is factory, cacel the factory key detection*/
if(g_boot_arg->sec_limit.magic_num == 0x4C4C4C4C)
{
if(g_boot_arg->sec_limit.forbid_mode == F_FACTORY_MODE)
{
//Forbid to enter factory mode
printf("%s Forbidden\n",MODULE_NAME);
factory_forbidden=1;
}
}
// forbid_mode = g_boot_arg->boot_mode &= 0x000000FF;
/*If boot reason is power key + volumn down, then
disable factory mode dectection*/
if(mtk_detect_pmic_just_rst())
{
factory_forbidden=1;
}
/*Check RTC to know if system want to reboot to Recovery*/
if(Check_RTC_Recovery_Mode())
{
g_boot_mode = RECOVERY_BOOT;
return ;
}
/*If MISC Write has not completed in recovery mode
and interrupted by system reboot, go to recovery mode to
finish remain tasks*/
if(unshield_recovery_detection())
{
return ;
}
ulong begin = get_timer(0);
/*we put key dectection here to detect key which is pressed*/
while(get_timer(begin)<50){
#ifdef MTK_FASTBOOT_SUPPORT
if(mtk_detect_key(MT_CAMERA_KEY))
{
dprintf(INFO,"[FASTBOOT]Key Detect\n");
g_boot_mode = FASTBOOT;
return ;
}
#endif
if(!factory_forbidden){
if(mtk_detect_key(MT65XX_FACTORY_KEY))
{
printf("%s Detect key\n",MODULE_NAME);
printf("%s Enable factory mode\n",MODULE_NAME);
g_boot_mode = FACTORY_BOOT;
//video_printf("%s : detect factory mode !\n",MODULE_NAME);
return ;
}
}
#ifdef MT65XX_RECOVERY_KEY
if(mtk_detect_key(MT65XX_RECOVERY_KEY))
{
printf("%s Detect cal key\n",MODULE_NAME);
printf("%s Enable recovery mode\n",MODULE_NAME);
g_boot_mode = RECOVERY_BOOT;
//video_printf("%s : detect recovery mode !\n",MODULE_NAME);
return ;
}
#endif
}
#endif
#ifdef MTK_KERNEL_POWER_OFF_CHARGING
if(kernel_power_off_charging_detection())
{
printf(" < Kernel Power Off Charging Detection Ok> \n");
return;
}
else
{
printf("< Kernel Enter Normal Boot > \n");
}
#endif
}
boot_mode_select函數中的g_boot_mode變量尤爲重要,不同的賦值決定着它boot什麼。在boot_mode_select函數中可以看到所有的分支,對boot_mode_select的賦值,要麼是NORMAL_BOOT要麼是FASTBOOT要麼是RECOVERY_BOOT,似乎都和充電界面沒什麼關係啊。但是注意函數的尾部:
#ifdef MTK_KERNEL_POWER_OFF_CHARGING
if(kernel_power_off_charging_detection())
{
printf(" < Kernel Power Off Charging Detection Ok> \n");
return;
}
else
{
printf("< Kernel Enter Normal Boot > \n");
}
#endif
看到了power_off_charging,似乎和關機狀態下的充電有關係,再看兩個分支的printf,一個分支和充電相關,一個分支是Kernel Enter Normal Boot正常開機啓動。馬上搜索kernel_power_off_charging_detection的定義:
find -name “*.c” | xargs grep “kernel_power_off_charging_detection”
在bootable\bootloader\lk\platform\mt6582\mt_kernel_power_off_charging.c中找到了這個函數,看文件名,顯然是和關機充電相關的代碼:
BOOL kernel_power_off_charging_detection(void)
{
int off_mode_status = 1;
/* */
if(is_force_boot()) {
upmu_set_rg_chrind_on(0);
printf("[%s] Turn off HW Led\n", __func__);
return FALSE;
}
off_mode_status = get_off_mode_charge_status();
printf("[%s] off_mode_status %d\n", __func__, off_mode_status);
if(upmu_is_chr_det() == KAL_TRUE) {
if (off_mode_status) {
g_boot_mode = KERNEL_POWER_OFF_CHARGING_BOOT;
} else {
g_boot_mode = NORMAL_BOOT;
upmu_set_rg_chrind_on(0);
return FALSE;
}
return TRUE;
}
else {
/* power off */
#ifndef NO_POWER_OFF
printf("[kernel_power_off_charging_detection] power off\n");
mt6575_power_off();
#endif
return FALSE;
}
/* */
}
可以看到這裏對g_boot_mode有兩種賦值,一種是關機狀態下充電,一種是正常開機。那麼一個改動思路就是將:
g_boot_mode = KERNEL_POWER_OFF_CHARGING_BOOT;
改成
g_boot_mode = NORMAL_BOOT;
這樣原本要進行關機充電的,改成了正常開機啓動。
還有一種找關鍵代碼的思路是,找到了g_boot_mode這個變量後,在LK代碼目錄中搜索,看看哪裏對它進行了賦值:
find -name “*.c” | xargs grep “g_boot_mode = ”
可以看到,只有bootable\bootloader\lk\platform\mt6582\mt_kernel_power_off_charging.c 文件中對g_boot_mode賦值爲KERNEL_POWER_OFF_CHARGING_BOOT,其它地方都是賦值爲NORMAL_BOOT、FASTBOOT、RECOVERY_BOOT、ALARM_BOOT等,明顯和充電無關。這樣也能快速定位到關鍵代碼,究竟是哪裏,讓它進入了關機充電那個界面。
此時還沒有結束,我又注意到了這個宏MTK_KERNEL_POWER_OFF_CHARGING:
#ifdef MTK_KERNEL_POWER_OFF_CHARGING
if(kernel_power_off_charging_detection())
{
printf(" < Kernel Power Off Charging Detection Ok> \n");
return;
}
else
{
printf("< Kernel Enter Normal Boot > \n");
}
#endif
在bootable\bootloader\lk\platform\mt6582\boot_mode.c的 boot_mode_select 函數中,如果代碼運行到這裏,又沒定義MTK_KERNEL_POWER_OFF_CHARGING這個宏,宏內這段代碼不會執行,那麼此時g_boot_mode的賦值是什麼?當然是初始值NORMAL_BOOT,當然這只是猜想,萬一哪個函數裏改變了它的值呢,接下來在這個位置插入打LOG的代碼把g_boot_mode的值輸出來看看,果然是NORMAL_BOOT。那麼嘗試取消定義MTK_KERNEL_POWER_OFF_CHARGING這個宏,是不是就能實現直接開機的效果呢?經過測試,果然如此。
這個宏的意思是是否允許在關機狀態下充電,可以理解爲,如果不允許,那麼插電後,設備會開機再充電。
當然,宏的改法是MTK平臺特有的,其它平臺可以參考代碼的改法,雖然不同平臺的LK代碼有差異,但總體流程還是不變的。
那麼,如何取消這個宏的定義呢:
通常這個宏位於ProjectConfig.mk中,具體路徑各不相同,如果找不到的話可以find -name “ProjectConfig.mk”搜索,另外有的同學改了以後還是無效,那可能不止這一處的mk文件定義了MTK_KERNEL_POWER_OFF_CHARGING哦,最簡單的辦法是直接全局搜索:
find -name “*.mk” | xargs grep “MTK_KERNEL_POWER_OFF_CHARGING = yes”
然後全部改爲MTK_KERNEL_POWER_OFF_CHARGING = no
改完以後重新編譯LK
mmm bootable/bootloader/lk:clean-lk
mmm bootable/bootloader/lk:lk
然後make snod 並 Download
USB插入,通電
Perfect!
總結
① 對於MTK平臺,關鍵在於MTK_KERNEL_POWER_OFF_CHARGING這個宏,搜索源代碼目錄下的所有mk文件:
find -name “*.mk” | xargs grep “MTK_KERNEL_POWER_OFF_CHARGING = yes”
將其替換爲爲MTK_KERNEL_POWER_OFF_CHARGING = no即可
② 如果是MTK平臺,但是沒有MTK_KERNEL_POWER_OFF_CHARGING這個宏,嘗試找到bootable\bootloader\lk\platform\mt6582\mt_kernel_power_off_charging.c文件
你的源碼不一定是這個路徑,嘗試搜索mt_kernel_power_off_charging.c
find -name “mt_kernel_power_off_charging.c”
裏面有個kernel_power_off_charging_detection(void)函數,函數內對g_boot_mode的賦值,通通改爲g_boot_mode = NORMAL_BOOT;
③ 對於高通等其它平臺,如果沒有方法②中的函數,需要花點功夫了,首先定位bootloader的lk部分代碼,然後搜索這些和啓動模式相關的關鍵詞:
g_boot_mode NORMAL_BOOT FASTBOOT RECOVERY_BOOT
搜索哪些地方對g_boot_mode賦值了:
find -name “*.c” | xargs grep “g_boot_mode = ”
着重去分析這些地方,配合打LOG調試,相信你很快就能搞定