痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

痞子衡發表於2018-06-12

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是飛思卡爾i.MX RT系列MCU的Raw NAND啟動

  前面鋪墊了七篇啟動系列文章,終於該講具體Boot Device了,我們知道i.MXRT支援的外部Boot Device共有6種(Serial NOR&NAND、Parallel NOR&NAND、SD/eMMC、SPI NOR/EEPROM),其中最常用的是Serial NOR&NAND,目前各大社群裡討論最火的也是Serial NOR/NAND啟動,有不少大神(硬漢eric2013, jicheng0622)已經寫過關於Serial NOR&NAND啟動的文章,寫得非常好,這讓痞子衡非常有壓力,因此痞子衡決定第一篇Boot Device寫較常用但還沒有人寫過的Raw(Parallel) NAND啟動,大家是不是很期待?(請配合說“是”),好,話不多說,開講。

一、支援的Raw NAND

  開門見山,i.MXRT支援載入啟動的主要是相容ONFI 1.0標準的Asynchronous SLC Raw NAND,至於資料線寬度,x8,x16都支援(一般x8應用比較多)。關於Raw NAND基本知識請先看一下痞子衡的另一篇文章 並行介面NAND標準(ONFI)及SLC Raw NAND簡介,本文後續的很多內容均是基於充分了解Raw NAND的前提下開展的。

Note1: ONFI是最流行的NAND標準,ONFI 1.0僅針對50MB/s低速Async SDR模式NAND,從ONFI 2.x開始逐步引入133MB/s、166MB/s、200MB/s高速Sync模式NAND的支援,從ONFI 3.x開始又引入400MB/s、533MT/s(NV-DDR2)更高速NAND的支援,從ONFI 4.x開始更是加入了667MT/s、800MT/s、1066MT/s、1200MT/s(NV-DDR3)超高速NAND的支援。
Note2: 關於NAND還有一個JEDEC標準JESD230,該標準是JEDEC與ONFI組織合作制定的,主要是定義了Async SDR、Sync DDR, Toggle DDR模式NAND的互操作性。JESD230標準版本可與ONFI 3.1及其之後的版本相對應。

  Raw NAND廠商非常多,對應Raw NAND晶片型號也很多,如果你在選型時不確定到底該為i.MXRT選擇哪一款Raw NAND時,可選用下面五款晶片,痞子衡均實測過:

Macronix MX30LF4GE8AB-TI        (x8 bits, 2KB Page/128KB Block/4Gb Device,  0bit ECC, 3.3V)
Micron MT29F4G08ABBDAWP         (x8 bits, 2KB Page/128KB Block/4Gb Device,  4bit ECC, 1.8V)
Micron MT29F4G08ABAFAWP:D       (x8 bits, 2KB Page/128KB Block/4Gb Device,  4bit ECC, 3.3V)
Micron MT29F16G08ABACAWP:C      (x8 bits, 4KB Page/512KB Block/16Gb Device, 4bit ECC, 3.3V)
Winbond W29N01GVSIAA            (x8 bits, 2KB Page/128KB Block/1Gb Device,  1bit ECC, 3.3V)

二、Raw NAND硬體連線

  確定了Raw NAND晶片選型後,底下便進入Raw NAND硬體電路設計及與i.MXRT的訊號連線環節:

  i.MXRT對於Raw NAND的底層介面支援是通過內部SEMC這個IP實現的,如下是SEMC的內部模組圖,從圖中我們可以看到,從內部資料匯流排來看SEMC支援AXI Bus/IP Bus兩種(此兩種方式會在後續eFUSE配置裡看到),而從外部介面來看SEMC最多能支援五種裝置(SDRAM, NAND, NOR, SRAM, 8080 Display),NAND是其中一種。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  雖然SEMC最多能支援五種裝置,但並不是同時支援的,同一時刻僅能支援一種裝置,因此SEMC介面訊號必然是複用的,下表是SEMC介面複用表,關於NAND介面訊號,需要特別說一下的是CE#訊號,從表中我們可以看到NAND的CE6#訊號有5個,即有5種配置選擇,但i.MXRT BootROM固定選擇的是SEMC_CSX[0],這點在設計NAND硬體連線時需要特別注意。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  如下是典型的NAND硬體連線設計,示例NAND晶片是MX30LF4GE8AB-TI(經典的TSOP-48封裝,晶片絲印上的L表明其是3.3V供電),其中WP#訊號沒有使能,並且供電選擇同時支援3.3V和1.8V(通過R302選擇,此處應連2-3),有朋友會疑問,為什麼此處要留有2路不同供電電壓?因為後期方便我們更換不同的供電輸入的NAND晶片。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

三、Raw NAND載入啟動過程

  確保Raw NAND硬體相關設計無誤之後,底下便是下載更新Bootable Image進Raw NAND以供BootROM載入啟動了,在下載Bootable image之前有必要先了解Raw NAND的載入啟動過程:

  痞子衡在啟動系列文章的第六篇 Bootable image格式與載入(elftosb/.bd) 裡的最後已經介紹過non-XIP image載入啟動過程,但實際上那個過程對於儲存在外部NAND Flash中Bootable image而言還是介紹得不夠全面,欠缺FCB/DBBT的處理流程,你肯定會疑問FCB/DBBT是什麼?這得從NAND與NOR差異說起,我們知道NOR Flash中所有空間都必須是可用的(即不允許有壞塊),這意味著NOR Flash中的Bootable image資料是可以按指定地址連續存放的(即所謂的線性儲存),並且Application可以原地XIP執行;但是NAND Flash中常常是有壞塊的(出廠壞塊,使用中產生壞塊),這就導致NAND可用空間地址不可預知並且有可能不連續,因此儲存在NAND中的Bootable image資料極有可能並不是連續存放的,並且Bootable image實際儲存的起始地址也不一定就是指定的起始地址(即所謂的非線性儲存)。
  舉例來說,如果NAND的block大小為128KB,Firmware(即Bootable Image)大小為260KB,我們指定從NAND地址0x40000處(即block index = 2)開始儲存Firmware,但是很不幸的是index為2、4的block均是壞塊,那麼實際上Firmware被分散儲存在了index為3、5、6三個block中,為了將來能正確地從NAND中讀回Firmware,我們需要額外記錄至少兩個資訊,一是指定的Firmware起始儲存地址0x40000,二是NAND中壞塊資訊block index 2、4。FCB/DBBT就是用來記錄這些額外的資訊。
  FCB大小為1KB,其主要記錄了Firmware資訊(地址,長度,份數),以及DBBT地址資訊。DBBT大小為1056bytes,其記錄了NAND晶片中所有的壞塊個數以及位置,DBBT即所謂的壞塊表。FCB/DBBT最大可有兩份,實際應用中一般只用一份即可,後面介紹均以一份FCB/DBBT為例講解,FCB0永遠從NAND地址0x0處(即index為0的block中的第1個Page)開始存放,DBBT0一般放在index為n的block裡(其實n是可設的,這在後面使用Flashloader時會講到,為求簡單我們常常設n=1),Firmware 0一般放在index為n+1的block裡(Firmware只允許從index為n+1的block及其之後開始存放,Firmware最大可以有8份)。關於FCB/DBBT結構原型,後續會進一步介紹。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  有了前面的背景知識,NAND的載入啟動過程便是上電之後,BootROM先從NAND起始地址處獲取FCB0資料,再根據FCB0裡的資訊獲取DBBT0資料以及Firmware 0起始地址,底下便進入跟NOR Flash一樣的載入過程,只不過在載入Firmware 0的過程中需要根據DBBT0壞塊表資訊自動跳過壞塊。如果在讀取Firmware 0時,發現部分Firmware資料所在的block是一個壞塊,但是這個block沒有被記錄在DBBT0中,這說明該壞塊是新產生的(該新壞塊資訊會在下一次下載Application時記錄在新DBBT中),存在該壞塊中的Firmware資料被破壞了,Firmware 0便失效了,BootROM便會嘗試按同樣的流程去載入Firmware 1、2...7,直到找到有效的Firmware,這就是為什麼在NAND中儲存多份Firmware的意義。

四、下載Application進Raw NAND

  理解了Raw NAND載入啟動過程,我們便可以開始使用Flashloader下載Application進Raw NAND晶片中:

  痞子衡在啟動系列文章的第四篇 Flashloader初體驗(blhost) 和第六篇 Bootable image格式與載入(elftosb/.bd) 裡分別介紹了Flashloader的基本使用以及如何將你的Application製作成Bootable image,後續內容假定你已經制作好一個Bootable image並且使用blhost工具與Flashloader建立了基本通訊,正要開始將Bootable image下載進Raw NAND。
  前面講過Raw NAND中除了要有Bootable image(Firmware)之外,還需要有FCB/DBBT,並且FCB/DBBT在Raw NAND中儲存的位置是比Bootable image靠前的,因此你遇到的第一個問題便是如何下載FCB/DBBT進Raw NAND?
  首先來看FCB和DBBT的原型,如下semc_nand_fcb_t是FCB原型,semc_nand_dbbt_t是DBBT原型:
  FCB/DBBT結構體開頭都是12bytes的semc_bcb_header_t,這個bcb header由Tag、Version、CRC Checksum(CRC32-MPEG2)組成,用於驗證FCB/DBBT的完整性。
  semc_nand_fcb_t.DBBTSerachAreaStartPage標明DBBT所在位置;semc_nand_fcb_t.searchStride和semc_nand_fcb_t.searchCount用於存在2份FCB/DBBT時標明第二份位置(此處我們僅用一份,所以searchCount設為1,searchStride的值不用管);semc_nand_fcb_t.firmwareCopies記錄Firmware總份數,semc_nand_fcb_t.firmwareTable標明所有Firmware具體位置;semc_nand_fcb_t.nandConfig是Raw NAND的configuration block,大小為256bytes,記錄Raw NAND特性引數。
  semc_nand_dbbt_t.badBlockNumber記錄壞塊總個數,semc_nand_dbbt_t.badBlockTable標明所有壞塊具體位置。

#define SEMC_NAND_BAD_BLOCKS_MAX_NUM 256
#define SEMC_NAND_FW_MAX_NUM 8

#define SEMC_NAND_FCB_TAG 0x4E464342U     //!< ASCII: "NFCB"
#define SEMC_NAND_FCB_VERSION 0x00000001  //!< Version: 1.0
#define SEMC_NAND_DBBT_TAG 0x44424254U    //!< ASCII: "DBBT"
#define SEMC_NAND_DBBT_VERSION 0x00000001 //!< Version: 1.0

typedef struct _nand_firmware_info
{
    uint32_t startPage;
    uint32_t pagesInFirmware;
} nand_firmware_info_t;

typedef struct _semc_bcb_header
{
    uint32_t crcChecksum; //!< [0x000-0x003]
    uint32_t fingerprint; //!< [0x004-0x007]
    uint32_t version;     //!< [0x008-0x00b]
} semc_bcb_header_t;

typedef struct __semc_nand_config
{
    semc_mem_config_t memConfig;      //!< [0x000-0x04f]
    uint8_t vendorType;               //!< [0x050-0x050]
    uint8_t cellTechnology;
    uint8_t onfiVersion;
    uint8_t acTimingTableIndex;
    uint8_t enableEccCheck;           //!< [0x054-0x054]
    uint8_t eccCheckType;
    uint8_t deviceEccStatus;
    uint8_t swEccAlgorithm;
    uint32_t swEccBlockBytes;         //!< [0x058-0x05b]
    uint8_t readyCheckOption;         //!< [0x05c-0x05c]
    uint8_t statusCommandType;        //!< [0x05d-0x05d]
    uint16_t readyCheckTimeoutInMs;   //!< [0x05e-0x05f]
    uint16_t readyCheckIntervalInUs;  //!< [0x060-0x061]
    uint8_t reserved0[30];            //!< [0x062-0x07f]
    uint8_t userOnfiAcTimingModeCode; //!< [0x080-0x080]
    uint8_t reserved1[31];            //!< [0x081-0x09f]
    uint32_t bytesInPageDataArea;     //!< [0x0a0-0x0a3]
    uint32_t bytesInPageSpareArea;
    uint32_t pagesInBlock;
    uint32_t blocksInPlane;           //!< [0x0ac-0x0af]
    uint32_t planesInDevice;          //!< [0x0b0-0x0b3]
    uint32_t reserved2[19];           //!< [0x0b4-0x0ff]
} semc_nand_config_t;

typedef struct _semc_nand_fcb
{
    semc_bcb_header_t bcbHeader;                              //!< [0x000-0x00b]
    uint32_t DBBTSerachAreaStartPage;                         //!< [0x00c-0x00f]
    uint16_t searchStride;                                    //!< [0x010-0x011]
    uint16_t searchCount;                                     //!< [0x012-0x013]
    uint32_t firmwareCopies;                                  //!< [0x014-0x017]
    uint32_t reserved0[10];                                   //!< [0x018-0x03f]
    nand_firmware_info_t firmwareTable[SEMC_NAND_FW_MAX_NUM]; //!< [0x040-0x07f]
    uint32_t reserved1[32];                                   //!< [0x080-0x0ff]
    semc_nand_config_t nandConfig;                            //!< [0x100-0x1ff]
    uint32_t reserved2[128];                                  //!< [0x200-0x3ff]
} semc_nand_fcb_t;

typedef struct _semc_nand_dbbt
{
    semc_bcb_header_t bcbHeader;                          //!< [0x000-0x00b]
    uint32_t reserved0;                                   //!< [0x00c-0x00f]
    uint32_t badBlockNumber;                              //!< [0x010-0x013]
    uint32_t reserved1[3];                                //!< [0x014-0x01f]
    uint32_t badBlockTable[SEMC_NAND_BAD_BLOCKS_MAX_NUM]; //!< [0x020-0x41f]
} semc_nand_dbbt_t;

  知道了FCB/DBBT結構,那麼怎麼生成FCB/DBBT資料並且下載進Raw NAND什麼地址處呢?當然我們可以手工建立FCB/DBBT並將其下載到Raw NAND中,但其實Flashloader工具會幫我們自動做好大部分工作(生成FCB/DBBT,將FCB/DBBT下載到Raw NAND中),而我們只需要提供簡化的12byte配置資料即可。如果你還有印象的話,痞子衡在啟動系列文章的第四篇 Flashloader初體驗(blhost) 的最後介紹過下載更新Application示例(該示例適用NAND晶片MX30LF4GE8AB-TI):

// 在SRAM裡臨時儲存Raw NAND配置資料
blhost -u -- fill-memory 0x2000 0x4 0xD0010101 // ONFI 1.0, non-EDO, Timing mode 0, 8bit IO, CSX0, HW ECC Check, inital HW ECC is enabled
blhost -u -- fill-memory 0x2004 0x4 0x00010101 // image copy = 1, search stride = 1, search count = 1
blhost -u -- fill-memory 0x2008 0x4 0x00020001 // Firmware block index = 2, block count = 1

// 使用Raw NAND配置資料去配置Raw NAND介面
blhost -u -- configure-memory 0x100 0x2000

  在上述示例裡痞子衡首先使用了fill-memory命令在0x2000地址處暫存了12byte配置資料,然後通過config-memory將這12byte資料裡的資訊配置到Flashloader的Raw NAND介面中,實際上這4個命令成功執行後,FCB/DBBT就已經被下載進Raw NAND裡面了。那麼這12byte配置資料到底是怎麼組織的?詳見下表:

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  從上表我們可以知道,其實這12byte資料提供的配置資訊還是比較多的,涵蓋NAND配置、FCB配置、Image配置,但是與FCB/DBBT原本的資料結構相比已經大幅精簡,我們還可以再進一步簡化,這12byte裡真正需要注意的只有四個地方(ECC status、ECC Type、IO Port Size、EDO mode),其餘可用固定配置。由於此處我們示例NAND晶片為MX30LF4GE8AB-TI,檢視NAND晶片手冊可知其是x8 IO且沒有HW ECC,那麼IO Port Size需設2'b01(即x8),ECC type、ECC status分別可設1'b1、1'b0(HW ECC Check,initial HW ECC is enabled,其實這樣設在沒有HW ECC的NAND晶片上的意思是不使能ECC Check),EDO mode可設1'b0(即non-EDO模式)。
  configure-memory命令執行成功之後,我們可以試著用read-memory從NAND晶片裡讀回FCB,DBBT確認一下,示例NAND晶片MX30LF4GE8AB-TI的page size為2KB,block size為128KB,那麼FCB應該在0x0處,DBBT應該在0x20000處,從0x0處讀回1KB資料發現其確實是有效的FCB,從FCB裡找到其中DBBTSerachAreaStartPage = 0x40,即DBBT放在index為64的Page裡(即index為1的Block起始Page裡),再從0x20000處讀回1056bytes資料發現其也確實是有效的DBBT。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  解決了第一個問題即FCB/DBBT問題,還有另一個問題便是image應該如何下載進Raw NAND?
  其實image的下載很簡單,只需要將Bootable image從index為2的block裡(與FCB裡的firmwareTable[0].startPage對應)開始下載即可,示例NAND晶片MX30LF4GE8AB-TI的block size為128KB,則下載地址應為0x40000,具體步驟如下:

// 擦除Raw NAND並將image下載進Raw NAND
blhost -u -- flash-erase-region 0x40000 0x20000 0x100    // Erase 1 block starting from block 2
blhost -u -- write-memory 0x40000 ivt_image.bin 0x100    // Program ivt_image.bin to block 2

  Bootable image下載成功之後,同樣我們可以試著用read-memory從NAND晶片裡讀回IVT,BootData,Application確認一下,Bootable image起始地址在0x40000,那麼IVT,BootData應該在0x40400,Application應該在0x42000,檢視資料發現確實是有效的Bootable image。你可能會疑問,NAND的讀寫操作一般都是按page的,為何我們使用read-memory命令提供的地址引數可以不按page對齊?其實Flashloader內部會有page快取區,Flashloader底層對NAND的訪問是按page進行的,並快取在內部page快取區,介面上層來讀寫NAND資料實際是在page快取區進行的,所以不受page對齊限制。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  至此,Application的下載工作便結束了。

五、進入Raw NAND啟動模式

  Application已經被成功下載進Raw NAND晶片之後,此時我們便可以開始設定晶片從Raw NAND啟動:

  在進入Boot Device選擇之前,你首先需要確定BOOT_MODE[1:0]=2'b10,即晶片處於Internal Boot模式,並且確認BT_FUSE_SEL(eFUSE偏移0x460處的32bit配置資料的bit4)為1'b0,這裡看不懂的朋友請溫習痞子衡前面的文章 Boot配置(BOOT Pin/eFUSE)
  設定好正確Boot模式後,再來選擇Boot Device,Boot Device由BOOT_CFG1[7:4]這四個pin的輸入狀態決定,下圖是RT105x/RT106x硬體板的參考設計,撥碼開關SW6應撥向SW_DIP-8的7,8,11,即設定BOOT_CFG[7:4]=4'b001x(4'b001x適用於i.MXRT105x/i.MXRT106x,對於i.MXRT102x此值應為4'b01xx),此時便進入了從SEMC NAND啟動模式。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  如果想確保i.MXRT晶片一定正在從Raw NAND啟動,可在晶片上電時使用Jlink偵錯程式或者藉助Flashloader讀取晶片內部2個暫存器的值,這2個暫存器分別是SRC_SBMR1/2, 我們設的關於啟動模式的BOOT_MODE pins/BOOT_CFG pin/eFUSE偏移0x450配置值在上電時會自動載入到SRC_SBMR1/2暫存器裡,BootROM主要是根據SRC_SBMR1/2暫存器的值來判斷啟動模式的。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

  PS: BOOT_MODE[1:0]也可以設為2'b00,即晶片處於Boot From Fuses模式,但此時稍微繁瑣一點,需要將BT_FUSE_SEL(eFUSE偏移0x460處的32bit配置資料的bit4)燒寫為1'b1和BOOT_CFG1[7:4](eFUSE偏移0x450處的32bit配置資料的bit7:4)燒寫成4'b001x(適用於i.MXRT105x/i.MXRT106x)。

六、配置eFUSE啟動Raw NAND

  設定好晶片啟動模式是從Raw NAND啟動之後,我們還需要最後關注一下與Raw NAND相關的具體特性配置:

  你應該記得我們在使用Flashloader下載Application的時候提供過12bytes的NAND配置資料,這12bytes的NAND配置資料是為了讓Flashloader能夠正確初始化Raw NAND介面去訪問NAND晶片(主要是寫FCB,DBBT,Bootable image),同樣BootROM上電也需要初始化Raw NAND介面去訪問NAND晶片(主要是讀FCB,DBBT,Bootable image),所以BootROM也需要類似這12bytes NAND配置資料,而BootROM的NAND配置便放在如下的eFUSE區域裡(i.MXRT105x/i.MXRT102x是一樣的,i.MXRT106x與i.MXRT105x比有細微調整),與Flashloader一樣這部分配置裡真正需要注意的也只有四個地方(ECC status、ECC Type、IO Port Size、EDO mode),其餘可用初始配置(即0值)。

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

痞子衡嵌入式:飛思卡爾i.MX RT系列MCU啟動那些事(8)- 從Raw NAND啟動

七、幾個注意事項

  1. i.MXRT105x A0版本與A1版本的BootROM有很大區別,本文內容僅適用於A1版本。
  2. 實測發現i.MXRT105x需要使能EDO模式方可訪問NAND(預設是AXI方式),而i.MXRT106x則不需要使能EDO模式。
  3. 配置資料(Flashloader的12bytes/BootROM的eFUSE)裡關於ECC的2處配置(ECC status、ECC Type)需根據實際連線的NAND晶片而定,不可隨意設定。
    3.1 對於沒有硬體ECC的NAND晶片,可有兩種設定組合:一、ECC Type=HW,ECC status=Enabled(即不用ECC check);二、ECC Type=SW,ECC status=x(即使用SW ECC check)。
    3.2 對於含有硬體ECC且預設是使能的NAND晶片,僅有一種設定組合:一、ECC Type=HW,ECC status=Enabled(即使用HW ECC check)。
    3.3 對於含有硬體ECC且預設沒使能的NAND晶片,可有兩種設定組合:一、ECC Type=SW,ECC status=x(即使用SW ECC check);二、ECC Type=HW,ECC status=Disabled(即使用HW ECC check,BootROM會自動使用set-feature命令開啟硬體ECC,目前僅支援Micron的NAND晶片)

  上述所有步驟全部完成之後,復位晶片你就應該能看到你放在Raw NAND裡的Application已經正常地啟動了。

  至此,飛思卡爾i.MX RT系列MCU的Raw NAND啟動痞子衡便介紹完畢了,掌聲在哪裡~~~

相關文章