我们知道 KBOOT 是一个完善的 Bootloader 解决方案,这个解决方案主要设计用于 Kinetis 芯片上,目前 Kinetis 芯片起码有上百种型号,KBOOT 在这上百种 Kinetis 芯片里存在的形式并不是完全一样的,KBOOT 主要有三种存在形式(ROM Bootloader、Flashloader、Flash-Resident Bootloader),下面痞子衡为大家细说这三种形态:
一、KBOOT 形态区别
KBOOT 有三种形态,分别是如下图所示的 ROM Bootloader、Flashloader、Flash-Resident Bootloader,三种形态共享大部分 KBOOT 源码,仅在一些细节上有差别,这些细节在 KBOOT 源码里是用条件编译加以区分的,对应的条件编译宏分别是 BL_TARGET_ROM, BL_TARGET_RAM, BL_TARGET_FLASH。三种形态最大的区别其实是在链接文件上,经过汇编器后的 read only section 分别链接在了 Kinetis 芯片 System memory 空间里的 ROM(起始地址 0x1c000000)、RAM(区间地址 0x20000000)、Flash(起始地址 0x00000000)区域。
下表是 KBOOT 三种形态的对比,分别从 use case、delivery mechanism、supported device、clock configuration、feature 五大角度进行了对比:
总结来说,可以这么看 KBOOT 这三种存在的由来:
对于 2014 年初及以后问世的 Kinetis 芯片(比如 MKL03、MKL27、MKL43、MKL80、MKE18F 等),芯片内基本都是含 ROM 空间的,因此 KBOOT 是以 ROM Bootloader 的形式存在的;
对于 2014 年初及以后主推的 Kinetis 芯片(比如 MK22、MK65、MKV31、MKS22 等),芯片内虽然没有 ROM 空间,但飞思卡尔希望能给客户提供至少一次免编程器烧录 Application(用于量产)的机会,因此 KBOOT 是以 Flashloader 的形式存在的;
对于在市场上主流又畅销的 Kinetis 芯片(比如 MKL25、MK22、MK66、MKL28 等),不管芯片内是否有 ROM 空间,飞思卡尔都希望能够给出 Bootloader 源码,以便让客户自由修改来满足其个性化需求,因此 KBOOT 是以 Flash-Resident Bootloader 的形式存在的;
二、KBOOT 各形态实现
2.1 ROM Bootloader
KBOOT 的 ROM Bootloader 形态是放在 ROM 空间里的,随着芯片一起 Tape-out 出厂,固化在芯片里面,所以该形态可以被当做硬件模块,可以被无限次使用。
因为有了 ROM 的存在,所以芯片上电启动便有了两种选择:从 ROM 启动、从内部 Flash 启动,这种启动选择是由芯片系统决定的。
如果是从 ROM 启动,那么我们可以借助 ROM 将 Application 烧写进 Flash(内部 / 外部)的起始空间并跳转过去执行。跳转至 Flash 执行分为:从内部 Flash 执行、从外部 QSPI NOR Flash 执行,这种执行选择是由 ROM 代码决定的。
如果已经使用 ROM 将 Application 下载进内部 Flash 起始地址,并在系统设置里设置芯片从内部 Flash 启动,那么下次芯片复位启动完全可以绕开 ROM 直接从内部 Flash 起始地址执行 Application。
2.2 Flash-Resident Bootloader
KBOOT 的 Flash-Resident Bootloader 形态是放在内部 Flash 起始空间的,以源代码的形式提供给客户,客户需要自己编译 KBOOT 工程并使用编程器 / 调试器将编译生成的 KBOOT binary 下载进芯片内部 Flash 起始地址,除非使用调试器将其擦除,否则其也可以被无限次使用。
对于没有 ROM 的芯片,芯片上电只能从内部 Flash 起始地址处开始启动,因为 Flash-Resident Bootloader 已经占据了内部 Flash 的起始空间,所以芯片永远是先执行 Flash-Resident Bootloader。借助 Flash-Resident Bootloader 只能将 Application 烧写进内部 Flash 一定偏移处(这个偏移地址由 Flash-Resident Bootloader 指定)并跳转过去执行。
2.3 Flashloader
KBOOT 的 Flashloader 形态其实也是放在内部 Flash 起始空间的,不过与 Flash-Resident Bootloader 形态在 Flash 里执行不同之处在于 Flashloader 形态是在 SRAM 里执行的,众所周知,SRAM 断电是不保存数据的,因此 Flashloader 需要一个放在内部 Flash 里的配套 loader 程序,在芯片上电时先运行 Flash 里的 loader 程序,由 loader 程序将 Flashloader 从 Flash 中搬运到 SRAM 中并跳转到 SRAM 中运行。
Flashloader 是在芯片出厂之后由飞思卡尔产品工程师将其 binary 预先下载进内部 Flash 再售卖给客户,所以客户拿到芯片之后至少可以使用一次 Flashloader,客户借助 Flashloader 可以将 Application 烧写进内部 Flash 起始空间(同时也覆盖了原 Flashloader-loader),这就是 Flashloader 只能被使用一次的原因。
2.3.1 loader 机制
关于 loader 机制与实现,有必要详细讲解一下,让我们结合代码分析,首先从官网下载 NXP_Kinetis_Bootloader_2_0_0.zip 包,就以 KS22 芯片为例(\targets\MKS22F25612\bootloader.eww):
使用 IAR EWARM 7.80.x 开发环境打开 KS22 的 Bootloader 工程,可以看到有如下三个工程,其中 flashloader.ewp 便是主角,其源码文件与 ROM 和 Flash-Resident Bootloader 是一样,只是工程链接文件有区别,其代码段链接在于 SRAM 里;maps_bootloader.ewp 便是 Flash-Resident Bootloader,不是此处讨论的重点;flashloader_loader.ewp 就是 Flashloader 能在 SRAM 里执行的关键所在。
flashloader_loader.ewp 工程里除了必要的芯片 startup 文件外,只有三个源文件:flashloader_image.c/h,bl_flashloader.c,其中 bl_flashloader.c 里包含了工程 main 函数,让我们试着分析这个文件以及 main 函数,下面是 bl_flashloader.c 的文件内容:
#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.
// 关键拷贝,实现了 flashloader binary 从 Flash 到 RAM 的转移
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.
// 关键跳转,执行位置从 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 的文件里我们可以看到,其实 loader 工程的 main 函数特别简单,它就是将内部 Flash 里的 g_flashloaderImage[]数据(即 flashloader.ewp 编译生成的 binary)拷贝到 g_flashloaderBase 地址(即 flashloader binary 起始地址)处,并将 SP 和 PC 分别指向 g_flashloaderStack(即 flashloader 初始 SP)和 g_flashloaderEntry(即 flashloader 的 Reset Handler 入口)。
那么 g_flashloaderXX 常量都放在哪里的呢?打开 \targets\MKS22F25612\iar\flashloader\output\Release\flashloader_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 机制越来越清晰了,现在只剩最后一个问题了,flashloader_image.c 文件是哪里来的?这个文件当然可以手动创建,文件里的信息都可以从 flashloader.ewp 工程生成的 elf/map 文件里中找到,但本着高效的原则,但凡能脚本自动生成的决不手动创建,是的这个 flashloader_image.c 文件就是脚本自动生成的,在 flashloader.ewp 的 Option 选项的 Build Actions 里可以看到调用脚本的命令,这个脚本名叫 create_flashloader_image.bat。
在 \bin 目录下存放了所有脚本文件,当然也包括 create_flashloader_image.bat,先打开这个脚本看一下:
cd /d %1
ielftool --bin output\%2\flashloader.elf flashloader.bin
python ..\..\..\..\bin\create_fl_image.py output\%2\flashloader.elf flashloader.bin output\%2\flashloader_image.c
ielftool.exe 是 IAR 软件目录下的工具,可以将 elf 文件转换成 bin 文件。最核心的脚本其实是 create_fl_image.py,这个 python 脚本根据 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')
# 创建了 elfData 对象,用于后续处理 .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 里获取关键信息
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 脚本里除了普通文件操作外,最关键的是这句 elfData = elf.ELFObject(),调用了 elf.py 文件提供的 elf 格式文件操作接口,通过这些接口得到了 flashloader 里的关键信息(vectors、resetHandler、stack),感兴趣的可以自己去分析 elf.py 文件。
三、KBOOT 各形态芯片支持
截止目前(2017 年),KBOOT 支持的 Kinetis 芯片全部列出在下表:
至此,飞思卡尔 Kinetis 系列 MCU 的 KBOOT 形态痞子衡便介绍完毕了,掌声在哪里~~~