痞子衡嵌入式:飞思卡尔Kinetis系列MCU启动那些事(2)- KBOOT形态(ROM/Bootloader/Flashloader)


  大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是飞思卡尔Kinetis系列MCU的KBOOT形态

  痞子衡在前一篇文章里简介了 KBOOT架构,我们知道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

typedef void (*call_function_t)(void);
call_function_t s_callFunction = 0;

////////////////////////////////////////////////////////////////////////////////
// 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
    s_callFunction = (call_function_t)g_flashloaderEntry;
    s_callFunction();
}

// @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形态痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

posted @ 2017-04-04 21:49  痞子衡  阅读(1446)  评论(0编辑  收藏  举报