從零開始構建bootloader(基於韋東山JZ2440V3開發板)

從零開始構建bootloader

項目步驟:html

第一階段:node

​ 一、關看門狗;linux

​ 二、時鐘初始化;c++

​ 三、內存初始化;緩存

​ 四、NandFlash初始化;架構

​ 五、代碼重定位(將flash中的代碼複製到指定的內存地址處,也即代碼段連接地址);異步

​ 六、跳轉到main函數;async

第二階段:ide

​ 七、寫main函數,在函數中設置要傳給內核的參數;svg

​ 八、跳轉到內核入口,啓動內核

​ 九、製做連接腳本

第三階段:

​ 十、編寫Makefile文件;

​ 十一、編譯、下載、運行


一、編寫start.S文件,初始化片上硬件

本文件須要完成的目標

1.關看門狗

2.設置時鐘

3.開啓指令緩存,初始化SDRAM

4.重定位(把bootloader自己的代碼從flash複製到它的連接地址(c函數編寫),而後清空bss段(c函數編寫))

5.跳轉到main函數。

#define CLKDIVN     0X4C000014  /*設置FCLK:HCLK:PCLK的比例*/
#define MPLLCON     0x4C000004  /*設置FCLK頻率*/
#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))

#define WTCON   0x53000000      /*看門狗寄存器*/
#define BWSCON  0X48000000      /*BANK寄存器*/

.text                   /*設置代碼段*/
.global _start          /*定義全局變量,要被連接腳本用到*/
_start:                 /*_start跳轉到這裏實現硬件初始化*/

    /* 1.關看門狗*/
    ldr r0, =WTCON
    mov r1, #0
    str r1, [r0]

    /* 2.設置時鐘(必須設爲異步總線模式) */
    ldr r0, =CLKDIVN
    mov r1, #5          /* FCLK:HCLK:PCLK=1:4:8 */
    str r1, [r0]

    mrc p15, 0, r1, c1, c0, 0   /*設置爲asynchronous bus mode*/
    orr r1, r1, #0xc0000000
    mcr p15, 0, r1, c1, c0, 0

    ldr r0, =MPLLCON            /* 設置時鐘頻率FCLK爲400MHZ */
    ldr r1, =S3C2440_MPLL_400MHZ
    str r1, [r0]

    /* 3.使能ICache,加快指令訪問速度;由於目前沒有開啓MMU,因此還不能開啓數據緩存DCache */
    mrc p15, 0, r0, c1, c0, 0   /* read control reg */
    orr r0, r0, #(1<<12)
    mcr p15, 0, r0, c1, c0, 0   /* write it back */

    /* 4.初始化SDRAM */
    ldr r0, =BWSCON
    adr r1, sdram_config        /* 使用adr跳轉,由於SDRAM未初始化 */
    add r3, r0, #(13*4)
1:
    ldr r2, [r1], #4
    str r2, [r0], #4
    cmp r0, r3
    bne 1b                      /*back to 1 when no equal*/

    /* 4.重定位 */
    ldr sp, =0x34000000         /*由於SDRAM大小爲64MB,因此堆棧指針設在最高地址處*/

    bl nand_init

    mov r0, #0                  /* src = r0 */
    ldr r1, =_start             /* dest = r1,_start爲代碼段的起始和連接地址0x30000000 */
    ldr r2, =__bss_start
    sub r2, r2, r1              /* len = r2,代碼段長度*/

    bl copy_code_to_sdram       /* 調用C函數copy_code_to_sdram(src, dest, len)*/
    bl clear_bss                /* 清除bss段*/

/* 5.執行main */
    ldr lr, =halt
    ldr pc, =main
    mov pc,lr                   /* 若main函數跳出後,使PC等於lr連接寄存器,避免程序跑飛 */
halt:
    b halt                      /* 死循環,避免跑飛 */

sdram_config:
    .long 0x22011110     //BWSCON
    .long 0x00000700     //BANKCON0
    .long 0x00000700     //BANKCON1
    .long 0x00000700     //BANKCON2
    .long 0x00000700     //BANKCON3  
    .long 0x00000700     //BANKCON4
    .long 0x00000700     //BANKCON5
    .long 0x00018005     //BANKCON6
    .long 0x00018005     //BANKCON7
    .long 0x008C04F4     // REFRESH
    .long 0x000000B1     //BANKSIZE
    .long 0x00000030     //MRSRB6
    .long 0x00000030     //MRSRB7

  • 關於時鐘頻率的設置解釋:

    • 將CPU頻率設爲最大值400MHZ(內核啓動時間7S變爲6S,由於HCLK和PCLK頻率沒有改變)
      而後分頻係數FCLK:HCLK:PCLK須要設置爲1:4:8。
    • 由於HCLK最高133MHZ,這裏須要設置爲100MHZ,PCLK最高50MHZ,因此這裏須要設置爲50HZ,因此得出 CLKDIVN寄存器須要等於0X5便可。
    • 具體爲何等於0x5,能夠參考下圖:

    image-20210109132950976

經過查看數據手冊,得知當FCLK取400MHZ時,設置MDIV爲0X5C,PDIV爲0x1,SDIV爲0x1。

  • 關於Cache的設置解釋:

    • 經過高速緩存存儲器能夠加快對內存的數據訪問,在CAHE中有ICAHE(指令緩存)和DCAHE(數據緩存):
      ICAHE: 指令緩存,用來存放執行這些數據的指令;
      DCAHE: 用來存放數據,須要開啓MMU才能開啓DCAHE。
      在沒開啓ICAHE以前,CPU讀取SDRAM地址數據時,每次都須要先訪問一次地址值,在讀數據.
      當開了ICAHE後,第一次讀取SDRAM地址數據時,ICAHE發現緩存裏沒有這個地址數據,而後將SDRAM中須要讀取的那部分一大塊內存數據都複製在緩存中,後面陸續讀取數據就不會再訪問SDRAM了,直到CPU沒有找到地址數據後ICAHE再從SDRAM中從新複製

    • 經過CP15協處理器來開啓ICAHE:ICAHE控制位在CP15的寄存器C1中位12(以下圖), 而後經過MRS和MSR向該位12置1,開啓ICAHE.因此代碼以下(放在SDRAM初始化以前):

    • mrc p15, 0, r0, c1, c0, 0      //將 CP15 的寄存器 C1 的值讀到 r0 中
      orr r0, r0, #(1<<12)         //將r0中位12置1     
      mcr p15,0, r0,c1,c0,0        //開啓ICAHE

二、編寫init.c,用於重定位,bss段清除,初始化NandFlash

(1)編寫nand_init()函數

準備知識:

我使用的NandFlash型號爲K9F2G08U0M,經過查閱芯片手冊獲知該flash大小=2048塊Block=128KPages=256MB=2Gb 。且其構成爲:

1個設備=2048(Block)

1塊Block=64(Pages)

1頁=(2K+64)(Byte) 由於每一個地址裏都存放了一個字節,因此用Byte表示,其中64B是存放ECC的OOB地址,(ECC:存放判斷位反轉的校驗碼)

image-20210106224622373

  • 寫過程:
    • 寫頁
    • 生成校驗碼ECC
    • 寫校驗碼到OOB頁中
  • 讀過程:
    • 讀出頁數據,並計算當前數據的ECC
    • 讀出存在OOB頁裏的原始ECC
    • 比較兩個校驗碼,相同則讀成功,不一樣則出現了位反轉,需從新讀取。

image-20210106225527801

image-20210106225610608

肯定通訊時序:

圖1(nandflash時序表)

圖2(nandflash時序圖)

圖3(2440-nandflash時序圖)

  • 經過圖2和圖1能夠看出:

tCS:等待芯片使能CE的時間, tCS=20nS

tCLS和tALS:等待WE(寫信號)結束的時間, tCLS=tALS=15nS

tWP:WE(寫信號)維持時間, tWP=15nS

tALH:等待命令寫入成功的時間, tALH=5nS

tCLH:等待地址寫入成功的時間, tCLH=5nS

  • 經過查看2440芯片手冊,nandflash時序圖,須要設置TACLS,TWRPH0和TWRPH1

TACLS:屬於等待WE(寫信號)就緒的時間,對比圖2得出TACLS= tCLS- tWP=0nS

TWRPH0:屬於WE(寫信號)的時間, 對比圖2得出TWRPH0= tWP=15nS

TWRPH1:屬於等待命令寫入成功的時間,對比圖2得出TWRPH1=tALH=tCLH=5nS

  • 在NFCONF寄存器中設置這三個參數

TACLS[13:12] :表示Duration(持續時間)=HCLK*TACLS,因爲Duration=0nS,因此TACLS=0

TWRPH0 [10:8] :表示Duration(持續時間)=HCLK*( TWRPH0+1),因爲Duration=15nS,HCLK=10nS(100Mhz),因此TWRPH0 =1.

TWRPH1 [6:4] :表示Duration(持續時間)= HCLK*( TWRPH1 +1),因爲Duration=5nS,HCLK=10nS(100Mhz),因此TWRPH1 =0

  • 代碼以下:
/* nand flash  時序 */#define TACLS      0
#define TWRPH0     1
#define TWRPH1     0

/*     nand  flash  寄存器       */
#define   NFCONF     *((volatile unsigend long *)0X4E000000); //配置寄存器(用來設置時序)
#define   NFCONT     *((volatile unsigend long *)0X4E000000); //控制寄存器(用來使能nandflash控制器以及ECC編碼器,還有控制芯片使能CE腳)
#define   NFCMMD    *((volatile unsigend char *)0X4E000000);//發送命令寄存器(命令只有8位)
#define   NFADDR     *((volatile unsigend char *)0X4E000000);//發送地址寄存器(地址只有8位)
#define   NFDATA      *((volatile unsigend int *)0X4E000000);//讀/寫數據寄存器(數據只有8位)
#define   NFSTAT      *((volatile unsigend int *)0X4E000000);//運行狀態寄存器(用於判斷RnB腳)

/*由於Nand Flash只有8位I/O腳,因此NFCMMD/ NFADDR/ NFDATA三個寄存器值都是unsigend char型  */

void nand_init(void)
{

 /* 設置時序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);

/* bit4=1:初始化ECC,  bit1=1:禁止片選  bit0=1:啓動nandflash控制器*/    
NFCONT = (1<<4)|(1<<1)|(1<<0);

}

(2)編寫nand_read()函數

在實現nand_read()函數前,還須要實現如下幾個子函數:nand_select()、nand_deselect()、nand_cmd()、nand_waite_idle()、nand_read_data();

  • 1. 片選使能函數(在讀寫FLASH以前都要選中片選)
void nand_select(void)               //使能片選
{
    int i;
    NFCONT&=~(1<<1);        // NFCONT控制器位1置0
    for(i=0;i<10;i++);           //等待芯片使能成功
}
  • 2. 取消片選函數(在退出讀寫FLASH時要取消片選)
void nand_deselect(void)                 //取消片選
{
    int i;
    NFCONT&=~(1<<1);        // NFCONT控制器位1置0
    for(i=0;i<10;i++);           //等待芯片使能成功
}
  • 3. nand寫命令
void nand_cmd(unsigned char cmd)
{
    volatile int i;
    NFCMMD = cmd;
    for (i = 0; i < 10; i++);
}
  • 4. 判斷RnB狀態函數(在寫入全部命令後都要判斷RnB腳是否爲高電平就緒)
void nand_wait_ready(void)
{
    while (!(NFSTAT & 1));
}
  • 5. 讀數據命令
unsigned char nand_data(void)
{
    return NFDATA;
}
  • 6. 寫地址命令

首先Nand Flash引腳只有8位,然而地址共有2048(塊)64(頁)2KB,爲了讀出多個地址,以下圖,因此須要分5個週期來實現發送地址:

image-20210106232716987

如上圖,其中 A10\~A0對應頁大小(列),因爲nandflash每頁2048B,因此只用到A10~A0;

A28~A11對應頁目錄(行),表示共有2048塊*64(每塊有64頁)個目錄

例如,4097 地址就是:

A10~A0=4097%2048= 1(A0=1,其他爲0)

A28~A11=4097/2048=2(A13=1,其他爲0)

void nand_addr(unsigned int addr)
{
    unsigned int col  = addr % 2048;
    unsigned int page = addr / 2048;
    volatile int i;

    NFADDR = col & 0xff;        /* A7~A0,第1週期 */
    for (i = 0; i < 10; i++);

    NFADDR = (col >> 8) & 0xff; /* A10~A8,第2週期 */
    for (i = 0; i < 10; i++);

    NFADDR  = page & 0xff;      /* A18~A11,第3週期 */
    for (i = 0; i < 10; i++);

    NFADDR  = (page >> 8) & 0xff;   /* A26~A19,第4週期 */
    for (i = 0; i < 10; i++);

    NFADDR  = (page >> 16) & 0xff;  /* A27~A28,第5週期 */
    for (i = 0; i < 10; i++);   
}
  • 7. nand 讀數據命令

**Nand Flash命令圖**

如上圖,例如:當要reset復位nand flash時:

1) 使能片選nand_select();

2) 發送0XFF復位命令nand_cmd(0xFF);

3) 等待RnB狀態是否就緒 nand_wait_idle();

4) 取消片選 nand_deselect();

Nand Flash讀數據時序圖

nand flash 讀數據分爲了如下幾個步驟:

(1) 使能片選CE,將CLE置1,等待發送命令

(2) 將WE置低,將IO置爲0X00,而後拉高WE,觸發一次上升沿,則將把0x00寫入flash中

(3) 將CLE置0,表示發送地址(分爲5個週期)

(4) 發送讀命令0X30

(5) 等待RnB信號爲高電平

(6) 讀數據(在同一頁裏,數據能夠連續讀,讀下一頁時,須要從新發送新的地址才行例如:讀1000地址到2050地址時,

1.發出1000地址,到達頁0的1000地址上,而後再連續讀(2048-1000)次,直到讀到頁0的2047處.

2.再發出2048地址,到達頁1的0地址上,而後連續讀(2051-2048)次,直到讀到2050爲止)

(7) 取消片選nCE

/* 
* src:源地址,爲32位地址,因此用unsigend int表示
* dest:目的地址內容,因爲這裏是將數據讀出到目的地址內容中,因此須要用到*指針,
*      由於每一個地址裏存的是一個字節,因此用unsigend char型 
*/
void nand_read(unsigned int  src,unsigned char  *dest,unsigned int  len)
{

int col = src % 2048;      //第一次讀,可能不是讀的頁首地址,因此須要記錄當前頁的位置
int i=0;                //當前讀了0次

nand_select();           //1使能片選nCE     
while(i<len)
{ 
    nand_cmd(0X00);         //2發送讀命令0X00  
    nand_addr(src);         // 3發送yuan地址(分爲5個週期)
    nand_cmd(0X30);         //4發送讀命令0X30    
    nand_wait_idle();       //5等待RnB信號爲高電平

   for(;(col<2048)&&(i<len);col++)      //連續讀頁內數據
   {
       dest[i]=nand_read_data();            //6.讀數據
       i++;
       src++;
   }
   col=0;
}
nand_deselect();                // 取消片選nCE
}

(3)編寫重定位函數:copy_code_tosdram()

/**************************************************/
/*                  重定位函數                     */
/**************************************************/

/* 複製代碼段(長度爲len = __bss_start- _start)到SDRAM連接地址dest(0x30000000)處*/
void copy_code_to_sdram(unsigned char* src, unsigned char* dest, unsigned int len)
{   
    unsigned int i = 0;

    /* 判斷nor啓動仍是nand啓動 */
    if (isBootFromNorFlash())
    {
        while (i < len)         /*Nor啓動,則直接複製*/
        {
            dest[i] = src[i];
            i++;
        }
    }
    else
    {
        nand_read((unsigned int)src, dest, len);
    }
}

(4)編寫isBootFramNorFlash()函數,來判斷nand啓動仍是nor啓動

/*******************************************************/
/*              判斷是NOR啓動仍是NAND啓動                */
/*******************************************************/

int isBootFromNorFlash(void)
{
    volatile int *p = (volatile int *)0;

    unsigned int tmp = *p;

    *p = 0x12345678;        /*向flash寫*/
    if (*p == 0x12345678)
    {
        *p = tmp;           /* 若能成功寫入,就是nand flash */
        return 0;
    }
    else
    {
        return 1;           /* 寫不成功,就是NOR flash*/
    }
}

(5)編寫clear_bss()函數

/******************************************************/
/*                  bss段清除函數                      */
/******************************************************/

void clear_bss(void)
{
    extern int __bss_start, __bss_end;
    int *p = &__bss_start;

    for (; p < &__bss_end; p++)
        *p = 0;
}

三、添加頭文件: setup.h和serial.h

(1)添加串口支持文件

  • 將串口uart0初始化文件serial.c添加到當前工程目錄中,並加以修改。
/*************************************************************/
/*                  初始化串口,實現終端顯示                    */
/*************************************************************/

/* 串口引腳的GPIO設置寄存器 */
#define GPHCON              (*(volatile unsigned long *)0x56000070)
#define GPHUP               (*(volatile unsigned long *)0x56000078)

/* UART registers*/
#define ULCON0              (*(volatile unsigned long *)0x50000000)
#define UCON0               (*(volatile unsigned long *)0x50000004)
#define UFCON0              (*(volatile unsigned long *)0x50000008)
#define UMCON0              (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0            (*(volatile unsigned long *)0x50000010)
#define UTXH0               (*(volatile unsigned char *)0x50000020)
#define URXH0               (*(volatile unsigned char *)0x50000024)
#define UBRDIV0             (*(volatile unsigned long *)0x50000028)

/* 串口發送狀態標誌*/
#define TXD0READY   (1<<2)

#define PCLK            50000000    // PCLK = 50MHz
#define UART_CLK        PCLK        //  UART0的時鐘=PCLK
#define UART_BAUD_RATE  115200      // 波特率115200
#define UART_BRD        ((UART_CLK  / (UART_BAUD_RATE * 16)) - 1)

/*
 * 初始化UART0
 * 115200,8N1,無校驗
 */
void uart0_init(void)
{
    GPHCON  |= 0xa0;    // GPH2,GPH3����TXD0,RXD0
    GPHUP   = 0x0c;     // GPH2,GPH3�ڲ�����

    ULCON0  = 0x03;     // 8N1(8������λ���޽��飬1��ֹͣλ)
    UCON0   = 0x05;     // ��ѯ��ʽ��UARTʱ��ԴΪPCLK
    UFCON0  = 0x00;     // ��ʹ��FIFO
    UMCON0  = 0x00;     // ��ʹ������
    UBRDIV0 = UART_BRD; // ������Ϊ115200
}

/*
 * 輸出單個字符
 */
void putc(unsigned char c)
{
    /* 等待串口準備好 */
    while (!(UTRSTAT0 & TXD0READY));

    /*向串口輸入字符 */
    UTXH0 = c;
}

/*
 * 輸出字符串
 */
void puts(char *str)
{
    int i = 0;
    while (str[i])
    {
        putc(str[i]);
        i++;
    }
}

/*
 * 輸出val的16進制數表示,主要用於調試檢查某內存地址的值是否正確
 */

void puthex(unsigned int val)
{
    /* 0x1234abcd */
    int i;
    int j;

    puts("0x");

    for (i = 0; i < 8; i++)
    {
        j = (val >> ((7-i)*4)) & 0xf;
        if ((j >= 0) && (j <= 9))
            putc('0' + j);
        else
            putc('A' + j - 0xa);

    }

}

(2)添加setup.h頭文件

  • 由於TAG結構體定義是存在u-boot-1.1.6/include/asm-arm/setup.h中,因此設置TAG參數須要用到這個文件,將setup.h複製到當前工程目錄下。

  • 修改setup.h文件,刪除如下不須要的代碼:

  • #define __tag __attribute__((unused, __section__(".taglist")))
    
    #define __tagtable(tag, fn) \
    
    static struct tagtable __tagtable_##fn __tag = { tag, fn }
    
    #define tag_member_present(tag,member)                                \
    
           ((unsigned long)(&((struct tag *)0L)->member + 1)  \
    
                     <= (tag)->hdr.size * 4)

    獲得如下setup.h文件:

#ifndef __ASMARM_SETUP_H
#define __ASMARM_SETUP_H

#define u8  unsigned char
#define u16 unsigned short
#define u32 unsigned long

/*
 * Usage:
 *  - do not go blindly adding fields, add them at the end
 *  - when adding fields, don't rely on the address until
 *    a patch from me has been released
 *  - unused fields should be zero (for future expansion)
 *  - this structure is relatively short-lived - only
 *    guaranteed to contain useful data in setup_arch()
 */
#define COMMAND_LINE_SIZE 1024

/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
    union {
    struct {
        unsigned long page_size;        /*  0 */
        unsigned long nr_pages;         /*  4 */
        unsigned long ramdisk_size;     /*  8 */
        unsigned long flags;            /* 12 */
#define FLAG_READONLY   1
#define FLAG_RDLOAD     4
#define FLAG_RDPROMPT   8
        unsigned long rootdev;          /* 16 */
        unsigned long video_num_cols;   /* 20 */
        unsigned long video_num_rows;   /* 24 */
        unsigned long video_x;          /* 28 */
        unsigned long video_y;          /* 32 */
        unsigned long memc_control_reg; /* 36 */
        unsigned char sounddefault;     /* 40 */
        unsigned char adfsdrives;       /* 41 */
        unsigned char bytes_per_char_h; /* 42 */
        unsigned char bytes_per_char_v; /* 43 */
        unsigned long pages_in_bank[4]; /* 44 */
        unsigned long pages_in_vram;    /* 60 */
        unsigned long initrd_start;     /* 64 */
        unsigned long initrd_size;      /* 68 */
        unsigned long rd_start;         /* 72 */
        unsigned long system_rev;       /* 76 */
        unsigned long system_serial_low;    /* 80 */
        unsigned long system_serial_high;   /* 84 */
        unsigned long mem_fclk_21285;       /* 88 */
    } s;
    char unused[256];
    } u1;
    union {
    char paths[8][128];
    struct {
        unsigned long magic;
        char n[1024 - sizeof(unsigned long)];
    } s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
};

/*
 * The new way of passing information: a list of tagged entries
 */

/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE   0x00000000

struct tag_header {
    u32 size;
    u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE   0x54410001

struct tag_core {
    u32 flags;      /* bit 0 = read-only */
    u32 pagesize;
    u32 rootdev;
};

/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM    0x54410002

struct tag_mem32 {
    u32 size;
    u32 start;  /* physical start address */
};

/* VGA text type displays */
#define ATAG_VIDEOTEXT  0x54410003

struct tag_videotext {
    u8      x;
    u8      y;
    u16     video_page;
    u8      video_mode;
    u8      video_cols;
    u16     video_ega_bx;
    u8      video_lines;
    u8      video_isvga;
    u16     video_points;
};

/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK    0x54410004

struct tag_ramdisk {
    u32 flags;  /* bit 0 = load, bit 1 = prompt */
    u32 size;   /* decompressed ramdisk size in _kilo_ bytes */
    u32 start;  /* starting block of floppy-based RAM disk image */
};

/* describes where the compressed ramdisk image lives (virtual address) */
/*
 * this one accidentally used virtual addresses - as such,
 * its depreciated.
 */
#define ATAG_INITRD 0x54410005

/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2    0x54420005

struct tag_initrd {
    u32 start;  /* physical start address */
    u32 size;   /* size of compressed ramdisk image in bytes */
};

/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x54410006

struct tag_serialnr {
    u32 low;
    u32 high;
};

/* board revision */
#define ATAG_REVISION   0x54410007

struct tag_revision {
    u32 rev;
};

/* initial values for vesafb-type framebuffers. see struct screen_info
 * in include/linux/tty.h
 */
#define ATAG_VIDEOLFB   0x54410008

struct tag_videolfb {
    u16     lfb_width;
    u16     lfb_height;
    u16     lfb_depth;
    u16     lfb_linelength;
    u32     lfb_base;
    u32     lfb_size;
    u8      red_size;
    u8      red_pos;
    u8      green_size;
    u8      green_pos;
    u8      blue_size;
    u8      blue_pos;
    u8      rsvd_size;
    u8      rsvd_pos;
};

/* command line: \0 terminated string */
#define ATAG_CMDLINE    0x54410009

struct tag_cmdline {
    char    cmdline[1]; /* this is the minimum size */
};

/* acorn RiscPC specific information */
#define ATAG_ACORN  0x41000101

struct tag_acorn {
    u32 memc_control_reg;
    u32 vram_pages;
    u8 sounddefault;
    u8 adfsdrives;
};

/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x41000402

struct tag_memclk {
    u32 fmemclk;
};

struct tag {
    struct tag_header hdr;
    union {
        struct tag_core     core;
        struct tag_mem32    mem;
        struct tag_videotext    videotext;
        struct tag_ramdisk  ramdisk;
        struct tag_initrd   initrd;
        struct tag_serialnr serialnr;
        struct tag_revision revision;
        struct tag_videolfb videolfb;
        struct tag_cmdline  cmdline;

        /*
         * Acorn specific
         */
        struct tag_acorn    acorn;

        /*
         * DC21285 specific
         */
        struct tag_memclk   memclk;
    } u;
};

struct tagtable {
    u32 tag;
    int (*parse)(const struct tag *);
};

#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type)  ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

#define for_each_tag(t,base)        \
    for (t = base; t->hdr.size; t = tag_next(t))

/*
 * Memory map description
 */
#define NR_BANKS 8

struct meminfo {
    int nr_banks;
    unsigned long end;
    struct {
        unsigned long start;
        unsigned long size;
        int           node;
    } bank[NR_BANKS];
};

extern struct meminfo meminfo;

#endif

四、編寫boot.c文件,用於存放main函數

(1)編寫main函數代碼:

void main(void)

{

void (*theKernel)(int zero, int arch, unsigned int params); 

/*定義一個函數指針theKernel,其中第一個參數zero:0           */

/* arch:機器ID ,因爲芯片類型不少,內核爲了辨別芯片而定義的機器ID,其中2440芯片的ID號是362,*/

/* params :tag參數位置,這裏咱們的tag起始地址=0x30000100*/

  /*1 初 始 化 串 口 0 , 使 內 核 能 打 印 信 息  */

  uart0_init();                                             //調用serial.h頭文件裏的uart0_init()
  puts(「uart0 init OK\r\n」);                                //打印uart0初始化  

  /*2從  nand   flash  裏 把 內 核 復 制 到  SDRAM  中 */

  puts(「copy  kernel  from  nand\r\n」);                  //打印內核複製
  nand_read((0x60000+64),0X30008000,0X200000);              //燒寫2MB,多燒寫點避免出錯       

/*

0x60000+64:表示內核在nand(存儲)地址上位置,

0X30008000:內核在sdram(運行)地址上位置

0X200000:內核長度2MB

由於Flash上存的內核格式是:uImage(64B頭部(header)  + 真正的內核 )

在uboot界面中輸入mtd命令能夠看到:

              kernel分區位於 nand的0X00060000~0x00260000

因此在nand中真正的內核地址=0x60000+64,

在uboot界面中輸入boot命令能夠看到:

            Data Size:    1848656 Bytes =1.8 MB

            Load Address: 30008000

因此內核目的地址=0X30008000

長度=1.8MB

*/

  /*3 設  置  T  A  G  參  數        */

 puts(「set  boot  params\r\n」);                  //打印設置參數信息
 setup_start_tag (void);                      //在0X30000100地址保存start_tag數據,
 setup_memory_tags (void);                      //保存memory_tag數據,讓內核知道內存多大
 setup_commandline_tag (「boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0」);  
 /*保存命令行bootargs參數,讓內核知道根文件系統位置在/dev/mtdblock3,指定開機運行第一個腳本/linuxrc,指定打印串口0*/
 setup_end_tag (void);                            //初始化tag結構體結束

 /*  4 跳 轉 執 行          */

   puts(「boot   kernel\r\n」);                      //打印啓動內核
   theKernel = (void (*)(int, int, unsigend int))0x30008000;
    // 設置theKernel地址=0x30008000,用於後面啓動內核
   theKernel(0,362,0x300000100);        //362:機器ID,  0x300000100: params(tag)地址
/*傳遞參數跳轉執行到0x30008000啓動內核,           */
/*至關於: mov r0,#0                             */
/*ldr r1,=362                                  */
/*ldr r2,= 0x300000100                         */
/*mov pc,#0x30008000                           */
  puts(「kernel  ERROR\r\n」);                  //打印內核啓動出錯

}

(2) 建立TAG參數 函數

建立tag參數函數代碼以下:

#include 「setup.h」              

static struct tag *params;               //定義個tag結構體變量params指針

void setup_start_tag (void)              //開始tag
{
    params = (struct tag *) 0x30000100;  //tag起始地址等於0X30000100
    params->hdr.tag = ATAG_CORE;         //頭部常量tag=0x54410001
    params->hdr.size = tag_size (tag_core);    //size=5,

    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;
    params = tag_next (params);     //parmas=( struct tag *)((u32 *)parmas+ params->hdr.size)  
}

// setup_start_tag (bd)保存tag參數以下:
setup_memory_tags (void)                //內存tag
{

int i;
params->hdr.tag = ATAG_MEM;               //頭部常量tag=0x54410002
params->hdr.size = tag_size (tag_mem32);       //size=4
params->u.mem.start = 0x30000000;            //SDRAM起始地址
params->u.mem.size = 0x4000000;             //SDRAM內存大小64M
params = tag_next (params);                  //指向下個tag
}  

// setup_memory_tag s(bd)保存tag參數以下:
int strlen(char  *str)          //uboot不依賴任何庫,因此須要本身寫strlen函數
{
   int  i=0;
   while(str[i])
   {
    i++;
   }
return i;
}

void strcpy(char  *dest, char  *src)
{  
   while((*dest++=*src++)!=’\0’&&*dest!=’\0’);  
}

setup_commandline_tag (char  *cmdline)     //命令行tag
/**cmdline :指向命令行參數                                                             */
/*通常爲:「boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0」     */
{
 int len=strlen(cmdline)+1;                       //計算cmdline長度,並加上結束符
params->hdr.tag = ATAG_CMDLINE;             //頭部常量tag=0x54410009
params->hdr.size =(sizeof (struct tag_header) +len+3) >> 2;    /*size=(字符串長度+頭部長度) >>2 */
/*「+3」表示:按4字節對齊,好比當總長度=(1,2,3,4)時,size=(總長度+3)>>2=1,實現4字節對齊         */ 
strcpy (params->u.cmdline.cmdline, cmdline);      //複製形參字符串到params->u.cmdline.cmdline
params = tag_next (params);                    //執行下個tag
}

setup_end_tag (void)                            //結束tag
{
 params->hdr.tag = 0;                   
 params->hdr.size = 0;
}

五、編寫連接腳本:boot.lds

(1)連接腳本語法提示:

一、1.lds連接腳本中每一個符號(:或者=)左右都要加上空格或TAB按鍵 ;

. = ALIGN(4); 
.rodata:{*(.rodata)} //這裏:左右沒有空格,將出錯,改成 .rodata : {*(.rodata*)}

二、lds連接腳本中{}和()後都不能加";"分號。

.rodata : {*(.rodata*)}; //這裏"}"後面加了";"逗號,將出錯

三、lds腳本sections中如果以當前地址.等於xxx時,.後面都要加空格;

.= 0x33f80000; //這裏.後面沒有加空格,出錯  
. = 0x33f80000; 
. = ALIGN(4);

四、lds腳本中定義符號時,都要使符號在前。

__bss_start = .; 
.bss : { *(.bss) *(COMMON) }
. = __bss_end; //這裏定義"__bss_end"符號出錯,應該改成   __bss_end = .;

(2)連接腳本說明

  • 連接腳本中的0x33f80000就是連接地址(即程序運行時,該代碼段將被連接到內存的此地址處),共512K空間存放bootloader;

  • 定義__bss_start和__bss_end符號,是用來在程序開始以前,將這些未定義的變量清0,節省內存且_bss_start-0x33f80000就等於代碼的大小(即copy_code_tosdram函數中len值)。

  • SECTIONS {
      . = 0x33f80000;
    
      . = ALIGN(4);
      .text : { *(.text) }
    
      . = ALIGN(4);
      .rodata : {*(.rodata*)} 
    
      . = ALIGN(4);
      .data : { *(.data) }
    
      . = ALIGN(4);
      __bss_start = .;
      .bss : { *(.bss)  *(COMMON) }
      __bss_end = .;
    }

(3)NAND FLASH分區說明

通常在頭文件中會經過MTDPARTS_DEFAULT宏定義,明確flash的分區設置,通常來講會分爲4個區,依次存放bootloader、啓動參數、內核映像、根文件系統。

bootloader 一開機直接運行u-boot
boot parameters 存放一些能夠設置的參數,供u-boot使用
kernel 存放內核區
root filesystem 根文件系統,掛載(mount)後才能使用文件系統中的應用程序

(4)NAND Flash啓動的架構和流程image-20201125230828425

啓動流程爲

  • 上電後,CPU內置程序會從NAND Flash的特定地址(通常是第一個block塊地址)讀出Boot-Loader程序到CPU的內部內存中。
  • CPU將控制權交給內部存儲器中的Boot-Loader;
  • Boot-Loader初始化SDRAM,再從NAND Flash中將主程序載入到SDRAM中;
  • Boot-Loader將控制權交給主程序。

六、編寫makefile文件

備註:在makefile中‘=’與‘:=’的區別:

‘=’ 無關位置的等於(好比:」x=a y=$(x) x=b」,那麼y的值永遠等於最後的值,等於 b ,而不是a)

‘:=’ 有關位置的等於(好比:」x:=a y:=$(x) x:=b」,那麼y的值取決於當時位置的值,等於 a ,而不是b)

CC      = arm-linux-gcc               //定義CC變量=arm-linux-gcc,簡化書寫,編譯命令,(*.C,*.S)文件生成*.O文件

LD      = arm-linux-ld                 //鏈接命令,將多個*.O文件生成 boot.elf       

AR      = arm-linux-ar                 //庫管理命令,這裏沒有用到 

OBJCOPY = arm-linux-objcopy            //複製/格式轉換命令, boot.elf生成boot.dis

OBJDUMP = arm-linux-objdump           //反彙編命令,boot.bin生成boot.dis

//GCC編譯參數,-Wall:顯示全部錯誤和警告, -O2:採用2級編譯優化
CFLAGS           := -Wall -O2

//添加頭文件參數,-nostdinc忽略缺省目錄, -fno-builtin不鏈接系統標準啓動文件和標準庫文件(表示不用自帶的strlen()等庫函數)
CPPFLAGS     := -nostdinc -fno-builtin       

//定義objs變量,包含生成boot.bin目標文件須要的依賴文件
objs := start.o init.o boot.o 

//執行生成目標文件,首先是先知足objs全部依賴文件都擁有,才執行
boot.bin: $(objs)     
    ${LD} -Tuboot.lds -o boot_elf $^
    ${OBJCOPY} -O binary -S boot_elf $@
    ${OBJDUMP} -D -m arm boot_elf > boot.dis

//-c編譯不鏈接。$@表示目標文件   $<表示第一個依賴文件
%.o:%.c
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<   

%.o:%.S
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

clean:
    rm -f *.bin *.elf *.dis *.o

七、下載編譯

(1)新建一個文件夾,並命名爲bootloader,將以上編寫好的:boot.c、init.c、start.S、setup.h、boot.lds和Makefile文件複製進去。而後將該bootloader文件夾經過共享文件夾或FileZilla軟件拷貝進虛擬機linux環境下。

(2)在Linux環境下經過終端命令行進入到該bootloader文件夾中,執行make命令,進行編譯、連接:

image-20210109214629292

(3)將獲得的二進制文件boot.bin拷貝到Windows環境下的D盤根目錄下。

(4)鍵盤「Win + R」組合鍵,輸入cmd回車,打開WIndows下的命令行終端,輸入命令d:進入D盤根目錄。

(5)打開開發板電源,將JTAG-USB轉接工具鏈接到電腦USB口,在WIndows命令行終端終端輸入oflash boot.bin命令,運行FLASH燒錄工具,將二進制目標文件燒錄進目標板的NAND Flash中:

image-20210109215334715

(6)選擇0回車,進入OpenJTAG模式,再依次鍵入1選擇S3C2440目標板、後面都鍵入0完成燒錄。

(7)用串口調試線鏈接開發板的COM口和電腦的USB口,打開MobaXterm串口鏈接工具,並鏈接到開發板。關閉開發板電源後,再從新接通電源,經過MobaXterm軟件觀察開發板啓動狀況。

(8)若是程序正確,將會看到開發板順利啓動內核並運行起來了linunx。

參考文章


獲取更多知識,請點擊關注:
嵌入式Linux&ARM
CSDN博客
簡書博客
知乎專欄

相關文章
相關標籤/搜索