i.MXRT1xxx沒有内部FLASH,需要外接FLASH存儲器以存儲image。衆所周知,FLASH從結構上分為NOR和NAND,i.MXRT啟動同時支援這兩種FLASH,NOR FLASH可以實作XIP,NAND FLASH不可以XIP,為了相容所有FLASH,在設計i.MXRT bootable image格式時必須以非XIP這種情況為基準。既然是非XIP執行,即意味着i.MXRT上電時會将image從外接FLASH拷貝到内部SRAM中去執行,在拷貝時必不可免要知道兩個重要的資料:image連結起始位址(決定image被拷貝到SRAM哪個位址)、image總長度(決定要從外部FLASH拷貝多長的image資料進SRAM),實際上除了這兩個最基本的資料外還有其他更進階的資料(配置、安全等特性),是以存儲在外接FLASH的i.MXRT Bootable image除了含有Application binary資料之外還必須含有額外的資訊,這些額外的資訊資料與Application binary共同組成i.MXRT Bootable image。
大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是恩智浦i.MX RT1xxx系列MCU的Bootable image格式與加載過程。
在i.MXRT1xxx啟動系列第三篇文章 Serial Downloader模式(sdphost, mfgtool) 裡痞子衡在介紹使用sdphost引導啟動Flashloader時使用過一個名叫ivt_flashloader.bin的image檔案,其實這個image檔案就是Bootable image的一種,雖然痞子衡簡單分析過ivt_flashloader的組成,但介紹得并不詳盡,今天痞子衡會為大家系統地講解i.MXRT Bootable image。
一、什麼是Bootable image?
如果你是一個有經驗的嵌入式開發者,肯定對image格式有所了解,我們通常開發的Application都是針對含内部FLASH的MCU而言的,比如Kinetis、LPC、STM32等MCU,其内部內建了一塊Parallel NOR FLASH,且FLASH位址是映射在ARM 4GB system address内的(一般從0x0位址開始),FLASH裡存儲的直接就是我們編譯連結後生成的原始Application binary(.bin),沒有任何多餘的資料組成。或許你會說還有.hex, .srec等其他image格式,是的,但這些帶位址資訊的image格式是為程式設計器或下載下傳器服務的,這些image格式經過程式設計器或者下載下傳器解析後真正下載下傳進MCU内部FLASH的資料還是原始Application binary。這類MCU上電後CPU能直接從内部FLASH擷取Application代碼并原地執行(XIP),是以對這類MCU而言,Bootable image就是存儲在内部FLASH的Application binary(.bin)。
但是以上經驗在開發i.MXRT時遇到了問題,i.MXRT沒有内部FLASH,需要外接FLASH存儲器以存儲image。衆所周知,FLASH從結構上分為NOR和NAND,i.MXRT啟動同時支援這兩種FLASH,NOR FLASH可以實作XIP,NAND FLASH不可以XIP,為了相容所有FLASH,在設計i.MXRT bootable image格式時必須以非XIP這種情況為基準。既然是非XIP執行,即意味着i.MXRT上電時會将image從外接FLASH拷貝到内部SRAM中去執行,在拷貝時必不可免要知道兩個重要的資料:image連結起始位址(決定image被拷貝到SRAM哪個位址)、image總長度(決定要從外部FLASH拷貝多長的image資料進SRAM),實際上除了這兩個最基本的資料外還有其他更進階的資料(配置、安全等特性),是以存儲在外接FLASH的i.MXRT Bootable image除了含有Application binary資料之外還必須含有額外的資訊,這些額外的資訊資料與Application binary共同組成i.MXRT Bootable image。至于這些額外的資訊在Bootable image裡是如何組織的,痞子衡在後面會繼續聊。
二、Bootable image連結空間
一個image的連結空間分兩種,一種是隻讀段(readonly code,data)的連結空間,另一種是讀寫段(readwrite data, STACK)的連結空間,這兩種連結空間要求的存儲媒體特性不一樣,痞子衡逐一講解:
前面講了i.MXRT同時支援外接NOR和NAND FLASH,其中NAND FLASH無法XIP,那麼存儲在NAND FLASH中的image隻讀段必須要連結在SRAM裡。i.MXRT内部有三種SRAM,分别是ITCM, DTCM, OCRAM,是不是這三種SRAM都可以被随意連結呢?答案并不是!因為在Boot期間,BootROM也需要占用SRAM,用于存放BootROM的讀寫段,是以被BootROM占用的SRAM無法用于連結image的隻讀段,如果強行連結,會導緻BootROM在拷貝image隻讀段時破壞自身讀寫段,進而發生不可預料的行為。下圖是RT1050 BootROM的memory map,從圖中可以得知BootROM占用的是0x20200000開始的OCRAM,并且看起來是整塊OCRAM都被占用了,是以不推薦使用OCRAM去連結image隻讀段。
黑科技:如果有朋友表示不服,RT1060/RT1050/RT1020的OCRAM是1MB/512KB/256KB,BootROM讀寫段不可能有這麼大,是的,痞子衡告訴你,其實BootROM資料段隻要32KB(0x20200000 - 0x20207FFF),另外還需要4KB用加載initial non-XIP image(0x20208000 - 0x20208FFF),是以對于存儲在non-XIP FLASH的image你可以從0x20209000之後的空間裡連結image隻讀段,而對于存儲在XIP FLASH的image你可以從0x20208000之後的空間裡連結image讀寫段,這個秘密一般人痞子衡是不會告訴他的。
前面講了存儲在NAND FLASH中的image隻讀段連結注意事項,而對于可以XIP的NOR FLASH,除了跟NAND一樣可以将隻讀段連結在SRAM外,還可以連結在i.MXRT配置設定給外接存儲器的XIP映射空間裡,下表給出了Serial NOR(QSPI)和Parallel NOR(SEMC)各自的映射起始位址,需要注意的是Serial NOR支援的最大XIP空間為504MB,但是Parallel NOR支援的最大XIP空間隻有16MB,别問痞子衡是怎麼知道的,痞子衡無所不知。
至于image的讀寫段,在連結時就不用差別Non-XIP/XIP FLASH了,都隻能放在SRAM裡,并且不用考慮BootROM對SRAM的占用問題(因為不在一個時間域裡被使用),隻要注意不和image自身隻讀段沖突就行。
黑科技:有朋友注意到了SDRAM,是的i.MXRT也支援SDRAM,通過SEMC接口去實作SDRAM讀寫,是以如果外接了SDRAM并且使能的話,也可以将image隻讀段/讀寫段放入SDRAM,關于SDRAM的使用,痞子衡會在後面文章裡介紹。
三、Bootable image七大組成
Bootable image是由一些額外的資訊資料與Application binary共同組成的,那些額外的資訊資料按功能分有6類,但這6類資訊資料并不都是必須的,其中有4類是可選的,是以一個Bootable image最多由7部分組成,最少由3部分組成。下面痞子衡按在FLASH裡存儲位置從低到高的順序逐一介紹組成Bootable image的7大部分:
3.1 偏移0x0000: FDCB(Flash Device Configuration Block)
第一個組成部分叫FDCB,是個可選組成,目前隻用于Serial/Parallel NOR FLASH,FDCB是從FLASH的起始位址處開始存放的,也是Bootable image最開始部分。FDCB最大4KB,其本身沒有統一的與FLASH無關的structure,具體structure根據啟動FLASH的接口類型(Serial/Parallel)而定,其一般是用來存儲目前連接配接的FLASH的具體特性參數,BootROM上電會使用通用且可靠的FLASH接口控制器配置(即BootROM中預設參數配置,一般是比較低速的配置)去通路外接FLASH并擷取FDCB,然後根據FDCB存儲的參數去重新配置FLASH接口控制器再去進一步通路FLASH。下面的結構體是Serial NOR的FDCB原型,此處痞子衡不會展開介紹這個結構體,留到後續介紹Serial NOR啟動再詳細介紹。
typedef struct _flexspi_nor_config
{
flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI
uint32_t pageSize; //!< Page size of Serial NOR
uint32_t sectorSize; //!< Sector size of Serial NOR
uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command
uint8_t isUniformBlockSize; //!< Sector/Block size is the same
uint8_t reserved0[2]; //!< Reserved for future use
uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3
uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command
uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false
uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution
uint32_t blockSize; //!< Block size
uint32_t reserve2[11]; //!< Reserved for future use
} flexspi_nor_config_t;
3.2 偏移0x0400/0x1000: IVT(Image Vector Table)
第二個組成部分叫IVT,是個必備組成,也是6類資訊資料裡的最核心資料,IVT是一個統一的與FLASH無關的structure,其原型如下面結構體所示,從結構體定義我們得知,IVT中記錄了Application、DCD、BD、CSF的位置資訊,這些資訊對BootROM加載啟動至關重要。IVT大小固定為32byte,其在Bootable image中的偏移位置也是固定的(對于XIP FLASH而言偏移是0x1000,對于Non-XIP FLASH而言偏移是0x400)。有朋友會疑問為何IVT偏移位址是固定的?其實答案很簡單,因為BootROM必須要首先擷取IVT才能進一步找到其他資訊資料,而IVT本身的位置資訊沒有在其他地方被标明,是以隻能在BootROM裡用一個常量來記錄。
#define HAB_TAG_IVT0 0xd1 /**< Image Vector Table V0 */
/** @ref hab_header structure */
typedef struct hab_hdr {
uint8_t tag; /**< Tag field */
uint8_t len[2]; /**< Length field in bytes (big-endian) */
uint8_t par; /**< Parameters field */
} hab_hdr_t;
/** @ref ivt structure */
struct hab_ivt_v0 {
/** @ref hdr with tag #HAB_TAG_IVT0, length and HAB version fields */
hab_hdr_t hdr;
/** Absolute address of the first instruction to execute from the image */
uint32_t entry;
/** Reserved in this version of HAB: should be NULL. */
uint32_t reserved1;
/** Absolute address of the image DCD: may be NULL. */
uint32_t dcd;
/** Absolute address of the Boot Data: may be NULL, but not interpreted any further by HAB */
uint32_t boot_data;
/** Absolute address of the IVT.*/
uint32_t self;
/** Absolute address of the image CSF.*/
uint32_t csf;
/** Reserved in this version of HAB: should be zero. */
uint32_t reserved2;
};
3.3 偏移0x0420/0x1020: BD(Boot Data)
第三個組成部分叫BD,是個必備組成,是僅次于IVT的核心資料,BD也是一個統一的與FLASH無關的structure,其原型如下面結構體所示,BD中記錄了Bootable image的起始位址與總長度。BD大小固定為16byte,BD資訊雖然記錄在了IVT中,但其在Bootable image中的偏移位置并不是任意的,BD是緊挨着IVT的。
/** @ref boot_data structure */
typedef struct boot_data{
uint32_t start; /* Start address of the image */
uint32_t size; /* Size of the image */
uint32_t plugin; /* Plugin flag */
uint32_t placeholder; /* placehoder to make even 0x10 size */
} BOOT_DATA_T;
3.4 DCD(Device Configuration Data)
第四個組成部分叫DCD,是個可選組成,目前主要用于SDRAM接口控制器(SEMC)的配置。由于i.MXRT内部SRAM size通常是夠用的,且通路速度也很快,是以SDRAM并不一定要被使能,Bootable image常常不會包含DCD,是以痞子衡在這裡先不做展開,後續有必要會再介紹。下面是SDK_2.3.1_EVKB-IMXRT1050包裡hello_world工程(flexspi_nor)所使用DCD示例:
#define DCD_TAG_HEADER (0xD2)
const uint8_t dcd_data[] = {
/*0000*/ DCD_TAG_HEADER,
0x04,0x30,0x41,0xCC,0x03,0xAC,0x04,0x40,0x0F,0xC0,0x68,0xFF,0xFF,0xFF,0xFF,
/*0010*/ 0x40,
0x0F,0xC0,0x6C,0xFF,0xFF,0xFF,0xFF,0x40,0x0F,0xC0,0x70,0xFF,0xFF,0xFF,0xFF,
...
/*0420*/ 0x00,
0x00,0x00,0x01,0xCC,0x00,0x0C,0x04,0x40,0x2F,0x00,0x4C,0x50,0x21,0x0A,0x09,
};
3.5 偏移0x2000: Application Binary
第五個組成部分是你最熟悉的Application binary,當然是個必備組成,其在Bootable image中的偏移位置是固定的(0x2000),關于Application本身這裡就不再贅述了。隻特别提一點,那就是i.MXRT的Application隻讀段(主要指ARM中斷向量表)并不可以從任意位址開始連結,有一個小小的限制,必須從標明的存儲器位址空間偏移0x2000之後開始連結(如選中ITCM,則必須要連結在0x00002000之後;如選中DTCM,則必須連結在0x20002000之後...),因為要預留至少8KB空間給IVT、BD、DCD等資料,這個限制是BootROM自身決定的,務必要注意。
3.6 CSF(Command Sequence File)
第六個組成部分叫CSF,是個特性組成,主要用于安全啟動的認證相關特性,痞子衡會在安全啟動裡進一步介紹。
3.7 KeyBlob
第七個組成部分叫KeyBlob,是個特性組成,主要用于安全啟動的加密相關特性,痞子衡會在安全啟動裡進一步介紹。
上圖是包含IVT、BD、DCD、Application、CSF的Bootable image的layout,這張圖很好地诠釋了IVT的作用。
四、Bootable image三種分類
前面介紹了Bootable image最多有7大組成,有些是必備,有些是可選,有的是特性。而在實際應用中,主要是必備+特性的組合形成如下三種常用分類:
- Unsigned Image: 這是最簡單的image類型,由IVT+BD+Application組成,主要用于産品開發階段。
- Signed Image: 這是較複雜的image類型,由IVT+BD+Application+CSF組成,一般用于産品釋出階段。
- Encrypted Image: 這是最複雜的image類型,由IVT+BD+Application+CSF+KeyBlob組成,主要用于對安全要求較高的産品中。
五、使用elftosb生成Bootable image
恩智浦官方提供了一個用于生成Bootable image的工具,名叫elftosb,這個工具就在\Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\elftosb目錄下,這個工具可以用來生成所有類型的Bootable image,指令格式固定如下:
elftosb.exe -f imx -V -c config_application.bd -o ivt_application.bin application.out
其中ivt_application.bin就是最終生成的Bootable image,指令所需要的2個輸入檔案分别是application.out、config_application.bd,application.out就是你的Application工程編譯連結生成的ELF檔案,config_application.bd是使用者配置檔案,這個.bd檔案主要是訓示elftosb工具如何在Application binary基礎上添加IVT、BD等其他資訊資料進而形成Bootable image,是以編寫.bd檔案是關鍵步驟,bd檔案有專門文法格式,但\Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\bd_file\imx10xx目錄下給了很多bd檔案示例,我們隻需要在某一個bd檔案基礎上修改即可
如果你追過痞子衡部落格文章,你應該知道痞子衡曾經實測過RT1052的coremark性能,coremark工程已經上傳到痞子衡的github https://github.com/JayHeng/cortex-m_app,工程路徑在\cortex-m_app\apps\coremark_imxrt1052\bsp\build\coremark.eww,編譯此工程可得到coremark_a000.out和coremark_a000.bin檔案,coremark程式隻讀段連結在ITCM位址(0x0000a000),我們來試着使用elftosb将coremark程式轉換成bootable image,bd檔案可參考imx-itcm-unsigned.bd,打開這個參考bd檔案:
options {
flags = 0x00;
# Note: This is an example address, it can be any non-zero address in ITCM region
startAddress = 0x8000;
ivtOffset = 0x400;
initialLoadSize = 0x2000;
# Note: This is required if the default entrypoint is not the Reset_Handler
# Please set the entryPointAddress to Reset_Handler address
// entryPointAddress = 0x60002411;
}
sources {
elfFile = extern(0);
}
section (0)
{
}
ivtOffset和initialLoadSize不用改,分别代表IVT和Application在Bootable image中的偏移位址,startAddress即BOOT_DATA_T.start,這個是可以修改的,牢記下面公式:
startAddress + initialLoadSize = Application隻讀段起始連結位址
coremark_a000.out是連結在0xa000位址處的,0x8000 + 0x2000 = 0xa000,是以此處startAddress也無需改,唯一需要确認的是entryPointAddress,保險起見統一将entryPointAddress設成Application的複位中斷位址,即entryPointAddress = 0x0000ecd1。bd檔案修改完成之後另存為config_coremark_a000.bd,讓我們試着執行下面指令:
elftosb.exe -f imx -V -c config_coremark_a000.bd -o ivt_coremark_a000.bin coremark_a000.out
分别打開coremark_a000.bin和ivt_coremark_a000.bin,可以看到ivt_coremark_a000.bin比coremark_a000.bin多了前8KB的資料,這前8KB裡包含了有效的IVT(偏移0x400)和BD(偏移0x420)。
六、Bootable image的加載過程
知道了Bootable image的構成,痞子衡最後再簡要為大家介紹一下i.MXRT BootROM是如何從外部存儲器中加載Bootable image進SRAM記憶體的。以non-XIP image加載為例(image連結在ITCM裡),下圖顯示了i.MXRT加載image的四個階段:
- 第一個階段即加載前,此時Bootable image完全存儲在外部Flash中,SRAM中沒有任何image資料;
- 第二階段即初始加載,BootROM首先會從外部Flash讀取Bootable image前4KB資料進SRAM臨時緩存區(OCRAM:0x20208000 - 0x20208FFF),我們知道這4KB資料裡包含了IVT和BD,BootROM從IVT和BD裡擷取到Bootable image的目标位址(BOOT_DATA_T.start)以及總長度(BOOT_DATA_T.size),此時便可以開始做進一步加載;
- 第三階段即内部轉移,由于BootROM已經從外部Flash讀取了4KB進SRAM臨時緩存區,為了避免重複讀取,BootROM會把這4KB資料首先複制到Bootable image的目标位址(ITCM);
- 第四階段即加載完成,BootROM會接着将剩下的Bootable image(BOOT_DATA_T.size - 4KB)從外部Flash中全部讀取出來存到目标區域(ITCM)完成全部加載。
至此,恩智浦i.MX RT1xxx系列MCU的Bootable image格式與加載過程痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時釋出到我的 部落格園首頁、CSDN首頁、微信公衆号 平台上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。
最後歡迎關注痞子衡個人微信公衆号【痞子衡嵌入式】,一個專注嵌入式技術的公衆号,跟着痞子衡一起玩轉嵌入式。
衡傑(痞子衡),目前就職于恩智浦MCU系統部門,擔任嵌入式系統應用工程師。
專欄内所有文章的轉載請注明出處:http://www.cnblogs.com/henjay724/
與痞子衡進一步交流或咨詢業務合作請發郵件至 [email protected]
可以關注痞子衡的Github首頁 https://github.com/JayHeng,有很多好玩的嵌入式項目。
關于專欄文章有任何疑問請直接在部落格下面留言,痞子衡會及時回複免費(劃重點)答疑。
痞子衡郵箱已被私信擠爆,技術問題不推薦私信,堅持私信請先掃碼付款(5元起步)再發。