DSP程序nandflash固化(三)——COFF文件解析與AIS腳本制作


   CCS生成的可執行文件是COFF或ELF文件,通常使用COFF文件作為下載文件。本節將對COFF文件做一定的介紹,並通過C語言數據結構讀取COFF文件並將其解析成內存映像,然后根據內存數據制作AIS腳本文件。
     程序中的代碼和數據在COFF文件中是以段的形式組織。燒寫COFF文件由幾種方法,其中一種是TI提供的hex6x工具,該工具可以將CCS生成的.out文件轉換成hex文件,再手工去除無用的信息,然后合並文件,最后將hex文件轉換成bin文件,bin文件可以直接燒寫到flash中,這種方法復雜,操作麻煩,而且需要安裝多種工具,其中的perl腳本工具更是非常難用,另一種方法則是使用TI提供的FLASHburn軟件,通過AISGen工具可以生成AIS腳本文件,但缺點是AISGen工具只提供DSK評估板中使用到的芯片對應的配置,對於其他芯片則無法使用,本文提供一種直接翻譯COFF文件,制作AIS文件的方法,可以方便的進行修改配置,並且代碼全部開源給大家參考。之所以開源還是因為筆者在研究DSP固化從NANDFLASH啟動的過程中,經歷了太多波折,網上的參考資料太少,大多講解不完全或者只有提供文字說明,沒有具體的代碼和操作步驟,為此筆者將這個過程分享出來,減少新手所走的彎路。
  首先我們需要了解COFF文件的組成結構,默認情況下,COFF文件包含3段:.text可執行代碼段,.data已初始化數據, .bss未初始化數據保留空間,在燒寫過程中只燒寫.text段和.data數據段。這里需要啰嗦的是,一個CCS工程中,並不是只有.text段和.data以及.bss段,除了這些段還有其他的段,如.cinit段,.switch段,還有用戶自定義的段,這里提到的.text和.data只是對代碼和數據的概括,從意義上來說.cinit也屬於.text段,但在COFF文件中.text段和.cinit等這些段不在用一個段中,但他們都需要下載到FLASH中,這一點需要讀者明白。
  下面來分析COFF文件的結構,COFF文件從上到下依次是:文件頭,可選文件頭,段頭信息表,段頭信息表對應的數據段,重定位信息,行號入口表,符號表,字符串表,如下圖所示:
 
  文件頭:描述了整個文件的基本信息,包含段的總數目,時間戳,符號表位置等,從文件的0偏移處開始,C語言描述的結構如下:
/*  COFF file header struct (22Bytes) */
typedef struct _FILE_HEADER
{
    unsigned short fileVersionID;  // Version ID
    unsigned short fileSecNum;     // Number of section headers
    unsigned int   fileTime;       // Time and date stamp
    unsigned int   filePointer;    // File pointer
    unsigned int   fileEntry;      // Number of entries in the symbol table
    unsigned short fileByte;       // Number of bytes in the optional header
    unsigned short fileFlag;       // 可選文件頭長度,如果有則為28否則為0
    unsigned short fileTargetID;   // Target ID,若為0x0099,目標文件可以在C6x平台上運行
}   FILE_HEADER, * PFILE_HEADER;
   這里只介紹本次需要用到的關鍵字段及其含義,下文的結構描述亦是如此。
fileSecNum: 段的總數,COF文件中包含的段落數,在解析過程中需要用到,循環讀取段落的次數。
fileFlag: 可選文件頭長度,如果有可選文件頭則為28,否則為0,C6000一般都有,所以在程序中認為有不做判斷。
fileTargetID: 若為0x0099則可在C6x上運行,一般的只需看看是不是就行了,在不確定解析文件是否正確的情況下,看看這個字段的值,如果是0x0099則可以認為文件頭解析沒有錯。
  可選文件頭:緊跟文件頭,22字節處開始,包含代碼大小,已初始化數據大小和未初始化數據大小,最終要的包含了程序入口地址,C語言描述如下:
// COFF file optional file header(28Bytes)
typedef struct _OPTIONAL_FILE_HEADER
{
    unsigned short optionMagic;    // Magic number
    unsigned short optionVersion;  // Version stamp
    unsigned int   optionExecut;   // Size of executable code
    unsigned int   optionInit;     // Size of initialized data
    unsigned int   optionUninit;   // Size of uninitialized data
    unsigned short EntryPointLo;   // Entry point
    unsigned short EntryPointHi;
    unsigned int   optionBeginCode;    // Beginning address of executable code
    unsigned int   optionBeginData;    // Beginning address of executable data
}OPTIONAL_FILE_HEADER;
      optionExecut: 可執行代碼的大小,單位字節;
     optionInit: 已初始化數據大小;
     EntryPointLo: 程序入口地址的低16位,EntryPointHi:程序入口地址的高16位。
到這里細心的讀者肯定會問,為什么這里不定義unsigned int EntryPoint,而是要分開定義兩個16位長度的地址,在各大百科上,關於COFF文件結構的介紹都是定義成unsigned long型數據,但在實驗過程中筆者發現無論定義成unsigned int還是unsigned long,讀出的數據都不是一個合理的數據,起初以為是字節沒有對齊導致的,在結構體定義前后加入#pragma pack(4)指令仍然出錯,但同樣是unsigned int類型的代碼數據大小就沒有問題(讀者可以字節在解析的時候計算optionExecut和.out文件大小對比,差不多大小說明解析無誤),至今筆者沒有查到原因何在,慶幸的是16位合並的方法處理得到的結果是正確的,如果讀者知道這其中的原因,希望讀者留言解釋,感激不盡!
  段頭:緊跟在可選段(如果有的話),長度固定為48B,所有的段頭連續存儲,C語言描述如下:
//section header(48Bytes)
typedef struct _SECTION_HEADER
{
    char             secCharacter[8];// Section name
    unsigned short   secPhyAddrLo;    // Section's physical address
    unsigned short   secPhyAddrHi;
    unsigned int     secVirAddr;      // Section's virtual address
    unsigned short   secSizeLo;       // Section size (Byte)
    unsigned short   secSizeHi;
    unsigned short   secRawLo;        // File pointer to raw data(段數據指針)
    unsigned short   secRawHi;
    unsigned int     secPointerRelo;  // File pointer to relocation entries段重定位表指針
    unsigned int     secReserved1;    // Reserved 行號表指針
    unsigned int     secNumRelo;      // Number of relocation entries重定位表長度
    unsigned int     secReserved2;    // Reserved行號表長度
    unsigned short   secFlagLo;       // 段標識
    unsigned short   secFlagHi;
    unsigned short   secReserved3;    // Reserved保留字段
    unsigned short   secMemory;       // Memory page number內存頁號
}   SECTION_HEADER;
secCharacter[8]: 段名,如.text, .bss等,當用戶自定義的段名長度超過8字節時以字符串符號表的指針表示;
secPhyAddrLo,secPhyAddrHi: 段的物理地址,即載入內存時的地址,section load指令的一個參數(前一節講過);
secSizeLo, secSizeHi: 段大小;
secRawLo, secRawHi: 段數據偏移,該段數據在.out文件中的偏移量;
secFlagLo, secFlagHi: 段標識,非常重要,區分是否寫入AIS腳本的唯一判決條件。
  這里需要注意的是Flag的值所表示的含義:
Flag值 說明
0x0020(0x100000) STYP_TEXT正文段標識
0x0040(0x1000000) STYP_DATA數據段標識,有些保存已初始化數據
0x0080(0x10000000) STYP_BSS保存的是未初始化的數據
  那么在程序判斷中應該如何判斷是否需要寫入AIS文件呢?其實上面的FLAG是字段標識,可以認為在FLAG的第6位和第七位分別標示代碼段和數據段,用戶定義的段在相應的位也要根據需要置1或置0,因此可以判斷第六位或第七位只要有一個是1則需要下載到FLASH中,也就是需要將這樣的段轉換成AIS腳本。
  下面分析主要的代碼:
首先讀取文件的段落頭和可選段落,
File_header = (FILE_HEADER *) &updateBuffer[0x0];
Opt_File_header = (OPTIONAL_FILE_HEADER *) &updateBuffer[0x16];
   在AIS起始位置開始寫入AIS魔術字和內部函數調用:
#define WRITEAIS(data) *(writePTR++)=data
WRITEAIS(0x41504954); //magic number
WRITEAIS(0x00000000); //Place holder reserved for number of pages over which image spans
WRITEAIS(0x00000000); //Place holder for block where image starts
WRITEAIS(0x00000000); //Place holder for page where image starts
WRITEAIS(0x5853590D); //function load
WRITEAIS(0x00030000); //set pll function
WRITEAIS(0x00000015); //PLLM = 0x15
WRITEAIS(0x00000000); //PLLDIV1 = 0
WRITEAIS(0x00000000); //internal oscillator
WRITEAIS(0x5853590D);
WRITEAIS(0x00050001); //SET EMIF
WRITEAIS(0x00840328); //AB1CR
WRITEAIS(0x00840328); //AB2CR
WRITEAIS(0x00000101); //AB3CR
WRITEAIS(0x00000001); //AB4CR
WRITEAIS(0x00000001); //NANDFCR
WRITEAIS(0x5853590D);
WRITEAIS(0x00090002); //SET DDR
WRITEAIS(0x00000030); //0 DDR PLLM
WRITEAIS(0x00000001); //1 DDR CLK
WRITEAIS(0x00000000); //2 0X00000000
WRITEAIS(0x00000000); //3 DDR clock
WRITEAIS(0x50006405); //4 DDRPHYCR
WRITEAIS(0x00138822); //5 SDBCR
WRITEAIS(0x22923209); //6 SDTIMR
WRITEAIS(0x0012C722); //7 SDTIMR2
WRITEAIS(0x000004EF); //8 SDRCR
//WRITEAIS(0x58535904); //disable CRC
WRITEAIS(0x58535903); //ENABLE CRC
   接着處理每一個段,順序讀取段落頭,根據段落頭中的信息讀取段落內容,寫入AIS:
// process each sector
sectionOffset = 0x32;
TOTAL_BYTE = 0;
for(i = 0; i < File_header->fileSecNum; i++)
{
     Sec_header = (SECTION_HEADER *) &updateBuffer[sectionOffset+i*sizeof(SECTION_HEADER)];
     Section_flag = TO32(Sec_header->secFlagLo, Sec_header->secFlagHi);
     Section_size = TO32(Sec_header->secSizeLo, Sec_header->secSizeHi);
 
     if((Section_flag & 0x60) && Section_size)
     {
         // 數據
         Section_addr = TO32(Sec_header->secRawLo, Sec_header->secRawHi);
         Section_size = TO32(Sec_header->secSizeLo, Sec_header->secSizeHi);
         boot_addr = TO32(Sec_header->secPhyAddrLo, Sec_header->secPhyAddrHi);
 
         WRITEAIS(0x58535901);
         WRITEAIS(boot_addr);
         WRITEAIS(Section_size);
 
         memcpy ((void*)writePTR, (void*)(updateBuffer+Section_addr), Section_size);
 
         if(Section_size % 4)
         {
              memset (((char*)writePTR)+Section_size, 0, 4 - Section_size % 4);
         }
         writePTR += ((Section_size+3)/4);
 
         TOTAL_SECT+=1;
         TOTAL_BYTE+=Section_size;
     }
}
  最后寫入結束跳轉指令:
boot_addr = TO32(Opt_File_header->EntryPointLo, Opt_File_header->EntryPointHi);
WRITEAIS(0x58535906);
WRITEAIS(boot_addr);
WRITEAIS(TOTAL_SECT);
WRITEAIS(TOTAL_BYTE);
   到此還沒有完,在魔術字之后又三個保留參數,第一個是所有寫入的頁數目,第二個是開始搜索的塊號,第三個是開始搜索的頁號,還記得前面講過的AIS需要存放在哪里嗎?沒錯,必須存放在第1塊之后,所以這里第二個參數寫入1.但是如果第一塊是壞塊怎么辦呢?順着往后延續,為此需要判斷一下:
writepage = (AISsize/NAND_PAGESIZE+1)/NAND_PAGES_PER_BLOCK + 1;
bad_blocks = 0;
writeblock = 1;//最少為第1塊
while ( (bad_blocks < 20) && bad_block_list[bad_blocks] != 0xFFFFFFFF &&
     (writeblock + writepage > bad_block_list[bad_blocks]) )
{
     writeblock = bad_block_list[bad_blocks];
     bad_blocks ++;
}
((int *)AISbuffer)[1] = (AISsize/NAND_PAGESIZE+1);
((int *)AISbuffer)[2] = writeblock;
((int *)AISbuffer)[3] = 0;
   至此,.out文件轉換為AIS腳本的工作已經全部完成,接下來就只需將AIS腳本寫入到NANDFLASH中即可。
本節重點:
  • COFF文件結構,由三種類型的段類型組成,需要解析每種段頭的字段內容;
  • 原始數據存放在所有段落頭的后面,必須為32位字寬寫入AIS;
  • 魔術字后面要寫入搜索起始的塊號和頁號,而且塊號必須大於0;
  • bootloader可以自動從1往后搜索,直到魔術字開始執行AIS腳本指令,在搜索過程中遇到壞塊可以自動跳過,但程序在寫入時要考慮壞塊的情況;
  • 跳轉結束指令要指定跳轉地址,這個地址在.out中不是連續存儲的四字節整型數據,需要分別解析高16位和低16位,最終合並成一個32位數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM