我們知道 KBOOT 是一個(gè)完善的 Bootloader 解決方案,這個(gè)解決方案主要設(shè)計(jì)用于 Kinetis 芯片上,目前 Kinetis 芯片起碼有上百種型號,KBOOT 在這上百種 Kinetis 芯片里存在的形式并不是完全一樣的,KBOOT 主要有三種存在形式(ROM Bootloader、Flashloader、Flash-Resident Bootloader),下面痞子衡為大家細(xì)說這三種形態(tài):
一、KBOOT 形態(tài)區(qū)別
KBOOT 有三種形態(tài),分別是如下圖所示的 ROM Bootloader、Flashloader、Flash-Resident Bootloader,三種形態(tài)共享大部分 KBOOT 源碼,僅在一些細(xì)節(jié)上有差別,這些細(xì)節(jié)在 KBOOT 源碼里是用條件編譯加以區(qū)分的,對應(yīng)的條件編譯宏分別是 BL_TARGET_ROM, BL_TARGET_RAM, BL_TARGET_FLASH。三種形態(tài)最大的區(qū)別其實(shí)是在鏈接文件上,經(jīng)過匯編器后的 read only section 分別鏈接在了 Kinetis 芯片 System memory 空間里的 ROM(起始地址 0x1c000000)、RAM(區(qū)間地址 0x20000000)、Flash(起始地址 0x00000000)區(qū)域。
下表是 KBOOT 三種形態(tài)的對比,分別從 use case、delivery mechanism、supported device、clock configuration、feature 五大角度進(jìn)行了對比:
總結(jié)來說,可以這么看 KBOOT 這三種存在的由來:
對于 2014 年初及以后問世的 Kinetis 芯片(比如 MKL03、MKL27、MKL43、MKL80、MKE18F 等),芯片內(nèi)基本都是含 ROM 空間的,因此 KBOOT 是以 ROM Bootloader 的形式存在的;
對于 2014 年初及以后主推的 Kinetis 芯片(比如 MK22、MK65、MKV31、MKS22 等),芯片內(nèi)雖然沒有 ROM 空間,但飛思卡爾希望能給客戶提供至少一次免編程器燒錄 Application(用于量產(chǎn))的機(jī)會(huì),因此 KBOOT 是以 Flashloader 的形式存在的;
對于在市場上主流又暢銷的 Kinetis 芯片(比如 MKL25、MK22、MK66、MKL28 等),不管芯片內(nèi)是否有 ROM 空間,飛思卡爾都希望能夠給出 Bootloader 源碼,以便讓客戶自由修改來滿足其個(gè)性化需求,因此 KBOOT 是以 Flash-Resident Bootloader 的形式存在的;
二、KBOOT 各形態(tài)實(shí)現(xiàn)
2.1 ROM Bootloader
KBOOT 的 ROM Bootloader 形態(tài)是放在 ROM 空間里的,隨著芯片一起 Tape-out 出廠,固化在芯片里面,所以該形態(tài)可以被當(dāng)做硬件模塊,可以被無限次使用。
因?yàn)橛辛?ROM 的存在,所以芯片上電啟動(dòng)便有了兩種選擇:從 ROM 啟動(dòng)、從內(nèi)部 Flash 啟動(dòng),這種啟動(dòng)選擇是由芯片系統(tǒng)決定的。
如果是從 ROM 啟動(dòng),那么我們可以借助 ROM 將 Application 燒寫進(jìn) Flash(內(nèi)部 / 外部)的起始空間并跳轉(zhuǎn)過去執(zhí)行。跳轉(zhuǎn)至 Flash 執(zhí)行分為:從內(nèi)部 Flash 執(zhí)行、從外部 QSPI NOR Flash 執(zhí)行,這種執(zhí)行選擇是由 ROM 代碼決定的。
如果已經(jīng)使用 ROM 將 Application 下載進(jìn)內(nèi)部 Flash 起始地址,并在系統(tǒng)設(shè)置里設(shè)置芯片從內(nèi)部 Flash 啟動(dòng),那么下次芯片復(fù)位啟動(dòng)完全可以繞開 ROM 直接從內(nèi)部 Flash 起始地址執(zhí)行 Application。
2.2 Flash-Resident Bootloader
KBOOT 的 Flash-Resident Bootloader 形態(tài)是放在內(nèi)部 Flash 起始空間的,以源代碼的形式提供給客戶,客戶需要自己編譯 KBOOT 工程并使用編程器 / 調(diào)試器將編譯生成的 KBOOT binary 下載進(jìn)芯片內(nèi)部 Flash 起始地址,除非使用調(diào)試器將其擦除,否則其也可以被無限次使用。
對于沒有 ROM 的芯片,芯片上電只能從內(nèi)部 Flash 起始地址處開始啟動(dòng),因?yàn)?Flash-Resident Bootloader 已經(jīng)占據(jù)了內(nèi)部 Flash 的起始空間,所以芯片永遠(yuǎn)是先執(zhí)行 Flash-Resident Bootloader。借助 Flash-Resident Bootloader 只能將 Application 燒寫進(jìn)內(nèi)部 Flash 一定偏移處(這個(gè)偏移地址由 Flash-Resident Bootloader 指定)并跳轉(zhuǎn)過去執(zhí)行。
2.3 Flashloader
KBOOT 的 Flashloader 形態(tài)其實(shí)也是放在內(nèi)部 Flash 起始空間的,不過與 Flash-Resident Bootloader 形態(tài)在 Flash 里執(zhí)行不同之處在于 Flashloader 形態(tài)是在 SRAM 里執(zhí)行的,眾所周知,SRAM 斷電是不保存數(shù)據(jù)的,因此 Flashloader 需要一個(gè)放在內(nèi)部 Flash 里的配套 loader 程序,在芯片上電時(shí)先運(yùn)行 Flash 里的 loader 程序,由 loader 程序?qū)?Flashloader 從 Flash 中搬運(yùn)到 SRAM 中并跳轉(zhuǎn)到 SRAM 中運(yùn)行。
Flashloader 是在芯片出廠之后由飛思卡爾產(chǎn)品工程師將其 binary 預(yù)先下載進(jìn)內(nèi)部 Flash 再售賣給客戶,所以客戶拿到芯片之后至少可以使用一次 Flashloader,客戶借助 Flashloader 可以將 Application 燒寫進(jìn)內(nèi)部 Flash 起始空間(同時(shí)也覆蓋了原 Flashloader-loader),這就是 Flashloader 只能被使用一次的原因。
2.3.1 loader 機(jī)制
關(guān)于 loader 機(jī)制與實(shí)現(xiàn),有必要詳細(xì)講解一下,讓我們結(jié)合代碼分析,首先從官網(wǎng)下載 NXP_Kinetis_Bootloader_2_0_0.zip 包,就以 KS22 芯片為例(targetsMKS22F25612bootloader.eww):
使用 IAR EWARM 7.80.x 開發(fā)環(huán)境打開 KS22 的 Bootloader 工程,可以看到有如下三個(gè)工程,其中 flashloader.ewp 便是主角,其源碼文件與 ROM 和 Flash-Resident Bootloader 是一樣,只是工程鏈接文件有區(qū)別,其代碼段鏈接在于 SRAM 里;maps_bootloader.ewp 便是 Flash-Resident Bootloader,不是此處討論的重點(diǎn);flashloader_loader.ewp 就是 Flashloader 能在 SRAM 里執(zhí)行的關(guān)鍵所在。
flashloader_loader.ewp 工程里除了必要的芯片 startup 文件外,只有三個(gè)源文件:flashloader_image.c/h,bl_flashloader.c,其中 bl_flashloader.c 里包含了工程 main 函數(shù),讓我們試著分析這個(gè)文件以及 main 函數(shù),下面是 bl_flashloader.c 的文件內(nèi)容:
#include <string.h>
#include "bootloader_common.h"
#include "fsl_device_registers.h"
#include "bootloader/flashloader_image.h"
#if DEBUG
#include "debug/flashloader_image.c"
#else
#include "release/flashloader_image.c"
#endif
////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////
// @brief Run the bootloader.
void bootloader_run(void)
{
? ? // Copy flashloader image to RAM.
? ? // 關(guān)鍵拷貝,實(shí)現(xiàn)了 flashloader binary 從 Flash 到 RAM 的轉(zhuǎn)移
? ? memcpy((void *)g_flashloaderBase, g_flashloaderImage, g_flashloaderSize);
? ? // Turn off interrupts.
? ? __disable_irq();
? ? // Set the VTOR to default.
? ? SCB->VTOR = 0x0;
? ? // Memory barriers for good measure.
? ? __ISB();
? ? __DSB();
? ? // Set main stack pointer and process stack pointer.
? ? __set_MSP(g_flashloaderStack);
? ? __set_PSP(g_flashloaderStack);
? ? // Jump to flashloader entry point, does not return.
? ? // 關(guān)鍵跳轉(zhuǎn),執(zhí)行位置從 Flash 切換到了 RAM
? ? void (*entry)(void) = (void (*)(void))g_flashloaderEntry;
? ? entry();
}
// @brief Main bootloader entry point.
int main(void)
{
? ? bootloader_run();
? ? // Should never end up here.
? ? while (1);
}
從上述 bl_flashloader.c 的文件里我們可以看到,其實(shí) loader 工程的 main 函數(shù)特別簡單,它就是將內(nèi)部 Flash 里的 g_flashloaderImage[]數(shù)據(jù)(即 flashloader.ewp 編譯生成的 binary)拷貝到 g_flashloaderBase 地址(即 flashloader binary 起始地址)處,并將 SP 和 PC 分別指向 g_flashloaderStack(即 flashloader 初始 SP)和 g_flashloaderEntry(即 flashloader 的 Reset Handler 入口)。
那么 g_flashloaderXX 常量都放在哪里的呢?打開 targetsMKS22F25612iarflashloaderoutputReleaseflashloader_image.c 可以找到答案:
const uint8_t g_flashloaderImage[] = {
? ? 0x70, 0x62, 0x00, 0x20, 0x11, 0xc4, 0xff, 0x1f, 0xeb, 0xc4, 0xff, 0x1f, 0x75, 0xef, 0xff, 0x1f,?
? ? // 此處省略 41552 bytes
? ? 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,?
};
const uint32_t g_flashloaderSize = 41584U;
const uint32_t g_flashloaderBase = 0x1fffc000;
const uint32_t g_flashloaderEntry = 0x1fffc411;
const uint32_t g_flashloaderStack = 0x20006270;
loader 機(jī)制越來越清晰了,現(xiàn)在只剩最后一個(gè)問題了,flashloader_image.c 文件是哪里來的?這個(gè)文件當(dāng)然可以手動(dòng)創(chuàng)建,文件里的信息都可以從 flashloader.ewp 工程生成的 elf/map 文件里中找到,但本著高效的原則,但凡能腳本自動(dòng)生成的決不手動(dòng)創(chuàng)建,是的這個(gè) flashloader_image.c 文件就是腳本自動(dòng)生成的,在 flashloader.ewp 的 Option 選項(xiàng)的 Build Actions 里可以看到調(diào)用腳本的命令,這個(gè)腳本名叫 create_flashloader_image.bat。
在 bin 目錄下存放了所有腳本文件,當(dāng)然也包括 create_flashloader_image.bat,先打開這個(gè)腳本看一下:
cd /d %1
ielftool --bin output%2flashloader.elf flashloader.bin
python ........bincreate_fl_image.py output%2flashloader.elf flashloader.bin output%2flashloader_image.c
ielftool.exe 是 IAR 軟件目錄下的工具,可以將 elf 文件轉(zhuǎn)換成 bin 文件。最核心的腳本其實(shí)是 create_fl_image.py,這個(gè) python 腳本根據(jù) elf 文件和 bin 文件生成了 flashloader_image.c 文件。打開 create_fl_image.py 文件如下(作了一些異常判斷的刪減,為了突出腳本主邏輯):
import sys
import os
import elf
# usage: create_fl_image.py <elffile> <binfile> <cfile>
def main(argv):
? ? # Collect arguments
? ? elfFilename = argv[0]
? ? binFilename = argv[1]
? ? cFilename = argv[2]
? ? # Open files
? ? binFile = open(binFilename, 'rb')
? ? cFile = open(cFilename, 'w')
? ? # 創(chuàng)建了 elfData 對象,用于后續(xù)處理 .elf 格式文件
? ? elfData = elf.ELFObject()
? ? with open(elfFilename, 'rb') as elfFile:
? ? ? ? # 開始處理輸入的 .elf 文件
? ? ? ? elfData.fromFile(elfFile)
? ? ? ? if elfData.e_type != elf.ELFObject.ET_EXEC:
? ? ? ? ? ? raise Exception("No executable")
? ? ? ? # 開始從 .elf 里獲取關(guān)鍵信息
? ? ? ? resetHandler = elfData.getSymbol("Reset_Handler")
? ? ? ? vectors = elfData.getSymbol("__Vectors")
? ? ? ? stack = elfData.getSymbol("CSTACK$$Limit")
? ? # Print header
? ? print >> cFile, 'const uint8_t g_flashloaderImage[] = {'
? ? # Print byte data
? ? totalBytes = 0
? ? while True:
? ? ? ? data = binFile.read(16)
? ? ? ? dataLen = len(data)
? ? ? ? if dataLen == 0: break
? ? ? ? totalBytes += dataLen;
? ? ? ? cFile.write(' ? ?')
? ? ? ? for i in range(dataLen):
? ? ? ? ? ? cFile.write('0x%02x, ' % ord(data[i]))
? ? ? ? print >> cFile
? ? print >> cFile, '};n'
? ? # Print size and other info
? ? cFile.write('const uint32_t g_flashloaderSize = %dU;n' % totalBytes)
? ? cFile.write('const uint32_t g_flashloaderBase = 0x%x;n' % vectors.st_value)
? ? cFile.write('const uint32_t g_flashloaderEntry = 0x%x;n' % resetHandler.st_value)
? ? cFile.write('const uint32_t g_flashloaderStack = 0x%x;n' % stack.st_value)
if __name__ == "__main__":
? ?main(sys.argv[1:])
create_fl_image.py 腳本里除了普通文件操作外,最關(guān)鍵的是這句 elfData = elf.ELFObject(),調(diào)用了 elf.py 文件提供的 elf 格式文件操作接口,通過這些接口得到了 flashloader 里的關(guān)鍵信息(vectors、resetHandler、stack),感興趣的可以自己去分析 elf.py 文件。
三、KBOOT 各形態(tài)芯片支持
截止目前(2017 年),KBOOT 支持的 Kinetis 芯片全部列出在下表:
至此,飛思卡爾 Kinetis 系列 MCU 的 KBOOT 形態(tài)痞子衡便介紹完畢了,掌聲在哪里~~~