1. gzyueqian
      18529173453
      首頁 > 新聞中心 > > 正文

      ArmLinux BOOTLOADER全程詳解

      更新時間: 2007-01-24 15:48:00來源: 粵嵌教育瀏覽量:899

        網上關于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比較龐大的程序,讀起來不太方便,編譯出的文件也比較大,而且更多的是面向開發用的引導代碼,做成產品時還要裁減,這一定程度影響了開發速度,對初學者學習開銷也比較大,在此分析一種簡單的BOOTLOADER,是在三星公司提供的2410 BOOTLOADER上稍微修改后的結果,編譯出來的文件大小不超過4k,希望對大家有所幫助.

        1.幾個重要的概念

        COMPRESSED KERNEL and DECOMPRESSED KERNEL

        壓縮后的KERNEL,按照文檔資料,現在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解壓器.因此要在ram分配時給壓縮和解壓的KERNEL提供足夠空間,這樣它們不會相互覆蓋.

        當執行指令跳轉到COMPRESSED KERNEL后,解壓器就開始工作,如果解壓器探測到解壓的代碼會覆蓋掉COMPRESSED KERNEL,那它會直接跳到COMPRESSED KERNEL后存放數據,并且重新定位KERNEL,所以如果沒有足夠空間,就會出錯.

        Jffs2 File System

        可以使armlinux應用中產生的數據保存在FLASH上,我的板子還沒用到這個.

        RAMDISK

        使用RAMDISK可以使ROOT FILE SYSTEM在沒有其他設備的情況下啟動.一般有兩種加載方式,我就介紹常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把這個地址通過啟動參數的方式ATAG_INITRD2傳遞給KERNEL.具體看代碼分析.

        啟動參數(摘自IBM developer)

        在調用內核之前,應該作一步準備工作,即:設置 Linux 內核的啟動參數。Linux 2.4.x 以后的內核都期望以標記列表(tagged list)的形式來傳遞啟動參數。啟動參數標記列表以標記 ATAG_CORE 開始,以標記 ATAG_NONE 結束。每個標記由標識被傳遞參數的 tag_header 結構以及隨后的參數值數據結構來組成。數據結構 tag 和 tag_header 定義在 Linux 內核源碼的include/asm/setup.h 頭文件中.

        在嵌入式 Linux 系統中,通常需要由 BOOTLOADER 設置的常見啟動參數有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

        (注)參數也可以用COMMANDLINE來設定,在我的BOOTLOADER里,我兩種都用了.

        2.開發環境和開發板配置:

        CPU:S3C2410,BANK6上有64M的SDRAM(兩塊),BANK0上有32M NOR FLASH,串口當然是逃不掉的.這樣,按照數據手冊,地址分配如下:

        0x4000_0000開始是4k的片內DRAM.

        0x0000_0000開始是32M FLASH 16bit寬度

        0x3000_0000開始是64M SDRAM 32bit寬度

        注意:控制寄存器中的BANK6和BANK7部分必須相同.

        0x4000_0000(片內DRAM)存放4k以內的BOOTLOADER IMAGE

        0x3000_0100開始存放啟動參數

        0x3120_0000 存放COMPRESSED KERNEL IMAGE

        0x3200_0000 存放COMPRESSED RAMDISK

        0x3000_8000 指定為DECOMPRESSED KERNEL IMAGE ADDRESS

        0x3040_0000 指定為DECOMPRESSED RAMDISK IMAGE ADDRESS

        開發環境:Redhat Linux,armgcc toolchain, armlinux KERNEL

        如何建立armgcc的編譯環境:建議使用toolchain,而不要自己去編譯armgcc,偶試過好多次,都以失敗告終.

        先下載arm-gcc 3.3.2 toolchain

        將arm-linux-gcc-3.3.2.tar.bz2 解壓到 /toolchain

        # tar jxvf arm-linux-gcc-3.3.2.tar.bz2

        # mv /usr/local/arm/3.3.2 /toolchain

        在makefile 中在把arch=arm CROSS_COMPILE設置成toolchain的路徑

        還有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否則庫函數就不能用了

        3.啟動方式:

        可以放在FLASH里啟動,或者用Jtag仿真器.由于使用NOR FLASH,根據2410的手冊,片內的4K DRAM在不需要設置便可以直接使用,而其他存儲器必須先初始化,比如告訴memory controller,BANK6里有兩塊SDRAM,數據寬度是32bit,= =.否則memory control會按照復位后的默認值來處理存儲器.這樣讀寫就會產生錯誤.

        所以步,通過仿真器把執行代碼放到0x4000_0000,(在編譯的時候,設定TEXT_BAS

        E=0x40000000)

        第二步,通過 AxD把linux KERNEL IMAGE放到目標地址(SDRAM)中,等待調用

        第三步,執行BOOTLOADER代碼,從串口得到調試數據,引導armlinux

        4.代碼分析

        講了那么多執行的步驟,是想讓大家對啟動有個大概印象,接著就是BOOTLOADER內部的代碼分析了,BOOTLOADER文章內容網上很多,我這里精簡了下,刪除了不必要的功能.

        BOOTLOADER一般分為2部分,匯編部分和c語言部分,匯編部分執行簡單的硬件初始化,C部分負責復制數據,設置啟動參數,串口通信等功能.

        BOOTLOADER的生命周期:

        1. 初始化硬件,比如設置UART(至少設置一個),檢測存儲器= =.

        2. 設置啟動參數,這是為了告訴內核硬件的信息,比如用哪個啟動界面,波特率 = =.

        3. 跳轉到Linux KERNEL的首地址.

        4. 消亡

        當然,在引導階段,象vivi等,都用虛地址,如果你嫌煩的話,就用實地址,都一樣.

        我們來看代碼:

      2410init.s

      .global _start//開始執行處

      _start:

      //下面是中斷向量

      b reset @ Supervisor Mode//重新啟動后的跳轉

      ……

      ……

      reset:

      ldr r0,=WTCON /WTCON地址為53000000,watchdog的控制寄存器 */

      ldr r1,=0x0 /*關watchdog*/

      str r1,[r0]



      ldr r0,=INTMSK

      ldr r1,=0xffffffff /*屏蔽所有中斷*/

      str r1,[r0]



      ldr r0,=INTSUBMSK

      ldr r1,=0x3ff /*子中斷也一樣*/

      str r1,[r0]

      /*Initialize Ports...for display LED.*/

      ldr r0, =GPFCON

      ldr r1, =0x55aa

      str r1, [r0]

      ldr r0, =GPFUP

      ldr r1, =0xff

      str r1, [r0]

      ldr r0,=GPFDAT

      ldr r1,=POWEROFFLED1

      str r1,[r0]

      /* Setup clock Divider control register

      * you must configure CLKDIVN before LOCKTIME or MPLL UPLL

      * because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflict

      nop

      * FCLK:HCLK:PCLK = 1:2:4 in this case

      */

      ldr r0,=CLKDIVN

      ldr r1,=0x3

      str r1,[r0]



      /*To reduce PLL lock time, adjust the LOCKTIME register. */

      ldr r0,=LOCKTIME

      ldr r1,=0xffffff

      str r1,[r0]

      /*Configure MPLL */

      ldr r0,=MPLLCON

      ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz

      str r1,[r0]

      ldr r1,=GSTATUS2

      ldr r10,[r1]

      tst r10,#OFFRST

      bne 1000f

      //以上這段,我沒動,就用三星寫的了,下面是主要要改的地方

      /* MEMORY C0NTROLLER(MC)設置*/

      add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放著MC初始化要用到的數據

      ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址

      add r2,r0,#52 // 復制次數,偏移52字

      1: //按照偏移量進行循環復制

      ldr r3,[r0],#4

      str r3,[r1],#4

      cmp r2,r0

      bne 1b

      .align 2


      MCDATA:

      .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

      上面這行就是BWSCON的數據,具體參數意義如下:

      需要更改設置DW6 和DW7都設置成10,即32bit,DW0 設置成01,即16bit

      下面都是每個BANK的控制器數據,大都是時鐘相關,可以用默認值,設置完MC后,就跳到調用main函數的部分

      .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))

      .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))

      .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))

      .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))

      .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))

      .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))

      .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))

      .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

      .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

      .word 0xB2 /* REFRESH Control Register */

      .word 0x30 /* BANKSIZE Register : Burst Mode */

      .word 0x30 /* SDRAM Mode Register */


      .align 2

      .global call_main //調用main函數,函數參數都為0

      call_main:

      ldr sp,STACK_START

      mov fp,#0 /* no previous frame, so fp=0*/

      mov a1, #0 /* set argc to 0*/

      mov a2, #0 /* set argv to NUL*/

      bl main /* call main*/

      STACK_START:

      .word STACK_BASE

      undefined_instruction:

      software_interrupt:

      prefetch_abort:

      data_abort:

      not_used:

      irq:

      fiq:

      /*以上是主要的匯編部分,實現了時鐘設置,串口設置watchdog關閉,中斷關閉功能(如果有需要還可以降頻使用),然后轉入main*/

      2410init.c file

      int main(int argc,char **argv)

      {

      u32 test = 0;

      void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL_BASE; //壓縮后的IMAGE地址

      int i,k=0;

      // downPt=(RAM_COMPRESSED_KERNEL_BASE);

      chkBs=(_RAM_STARTADDRESS);//SDRAM開始的地方

      // fromPt=(FLASH_LINUXKERNEL);

      MMU_EnableICache();

      ChangeClockDivider(1,1); // 1:2:4

      ChangeMPllValue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz

      Port_Init();//設置I/O端口,在使用com口前,必須調用這個函數,否則通信芯片根本得不到數據

      Uart_Init(PCLK, 115200);//PCLK使用默認的200000,撥特率115200

      /*******************(檢查ram空間)*******************/

      Uart_SendString("\n\tLinux S3C2410 Nor BOOTLOADER\n");

      Uart_SendString("\n\tChecking SDRAM 2410loader.c...\n");

      for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//


      //根據我的經驗,以一個字節為遞增,我們的板子,在256byte遞增檢測的時候是沒問題的,但是

      //以1byte遞增就出錯了,第13跟數據線隨幾的會冒”1”,檢測出來是硬件問題,現象如下

      //用仿真器下代碼測試SDRAM,開始沒貼28F128A3J FLASH片子,測試結果很好,但在上了FLASH片子//之后,測試數據(data)為0x00000400連續成批寫入讀出時,操作大約1k左右內存空間就會出錯,//而且隨機。那個出錯數據總是變為0x00002400,數據總線10位和13位又沒短路發生。用其他數據//測試比如0x00000200;0x00000800沒這問題。dx幫忙。

      //至今沒有解決,所以我用不了Flash.

      {

      chkPt1 = chkBs;

      *(u32 *)chkPt1 = test;//寫數據

      if(*(u32 *)chkPt1==1024))//讀數據和寫入的是否一樣?

      {

      chkPt1 += 4;

      Led_Display(1);

      Led_Display(2);

      Led_Display(3);

      Led_Display(4);

      }

      else

      goto error;

      }

      Uart_SendString("\n\tSDRAM Check Successful!\n\tMemory Maping...");

      get_memory_map();

      //獲得可用memory 信息,做成列表,后面會作為啟動參數傳給KERNEL

      //所謂內存映射就是指在4GB 物理地址空間中有哪些地址范圍被分配用來尋址系統的 RAM 單元。

      Uart_SendString("\n\tMemory Map Successful!\n");

      //我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面這段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.

      /*******************(copy linux KERNEL)*******************/

      Uart_SendString("\tLoading KERNEL IMAGE from FLASH... \n ");

      Uart_SendString("\tand copy KERNEL IMAGE to SDRAM at 0x31000000\n");

      Uart_SendString("\t\tby LEIJUN DONG dongleijun4000@hotmail.com \n");

      for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M

      * (u32 *)downPt = * (u32 *)fromPt;

      /*******************(load RAMDISK)*******************/

      Uart_SendString("\t\tloading COMPRESSED RAMDISK...\n");

      downPt=(RAM_COMPRESSED_RAMDISK_BASE);

      fromPt=(FLASH_RAMDISK_BASE);

      for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M

      * (u32 *)downPt = * (u32 *)fromPt;

      /******jffs2文件系統,在開發中如果用不到FLASH,這段也可以不要********/

      Uart_SendString("\t\tloading jffs2...\n");

      downPt=(RAM_JFFS2);

      fromPt=(FLASH_JFFS2);

      for(k = 0;k < (1024*1024/32);k++,downPt += 1,fromPt += 1)

      * (u32 *)downPt = * (u32 *)fromPt;

      Uart_SendString( "Load Success...Run...\n ");

      /*******************(setup param)*******************/

      setup_start_tag();//開始設置啟動參數

      setup_memory_tags();//內存印象

      setup_commandline_tag("console=ttyS0,115200n8");//啟動命令行

      setup_initrd2_tag();//root device

      setup_RAMDISK_tag();//ramdisk image

      setup_end_tag();

      /*關I-cache */

      asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i));

      i &= ~0x1000;

      asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i));

      /* flush I-cache */

      asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));

      //下面這行就跳到了COMPRESSED KERNEL的首地址

      theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));

      //啟動kernel時候,I-cache可以開也可以關,r0必須是0,r1必須是CPU型號

      (可以從linux/arch/arm/tools/mach-types中找到),r2必須是參數的物理開始地址

      /*******************END*******************/

      error:

      Uart_SendString("\n\nPanic SDRAM check error!\n");

      return 0;

      }

      static void setup_start_tag(void)

      {

      params = (struct tag *)RAM_BOOT_PARAMS;//啟動參數開始的地址

      params->hdr.tag = ATAG_CORE;

      params->hdr.size = tag_size(tag_core);

      params->u.core.flags = 0;

      params->u.core.pagesize = 0;

      params->u.core.rootdev = 0;

      params = tag_next(params);

      }

      static void setup_memory_tags(void)

      {

      int i;



      for(i = 0; i < NUM_MEM_AREAS; i++) {

      if(memory_map[i].used) {

      params->hdr.tag = ATAG_MEM;

      params->hdr.size = tag_size(tag_mem32);

      params->u.mem.start = memory_map[i].start;

      params->u.mem.size = memory_map[i].len;

      params = tag_next(params);

      }

      }

      }


      static void setup_commandline_tag(char *commandline)

      {

      int i = 0;

      /* skip non-existent command lines so the kernel will still

      * use its default command line.

      */

      params->hdr.tag = ATAG_CMDLINE;

      params->hdr.size = 8;

      //console=ttyS0,115200n8

      strcpy(params->u.cmdline.cmdline, p);

      params = tag_next(params);

      }

      static void setup_initrd2_tag(void)

      {

      /* an ATAG_INITRD node tells the kernel where the compressed

      * ramdisk can be found. ATAG_RDIMG is a better name, actually.

      */

      params->hdr.tag = ATAG_INITRD2;

      params->hdr.size = tag_size(tag_initrd);

      params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE;

      params->u.initrd.size = 2047;//k byte

      params = tag_next(params);

      }

      static void setup_ramdisk_tag(void)

      {

      /* an ATAG_RAMDISK node tells the kernel how large the

      * decompressed ramdisk will become.

      */

      params->hdr.tag = ATAG_RAMDISK;

      params->hdr.size = tag_size(tag_ramdisk);

      params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE;

      params->u.ramdisk.size = 7.8*1024; //k byte

      params->u.ramdisk.flags = 1; // automatically load ramdisk

      params = tag_next(params);

      }

      static void setup_end_tag(void)

      {

      params->hdr.tag = ATAG_NONE;

      params->hdr.size = 0;

      } void Uart_Init(int pclk,int baud)//串口是很重要的

      {

      int i;

      if(pclk == 0)

      pclk = PCLK;

      rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO disable

      rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC disable



      //UART0

      rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits

      下面這段samsung好象寫的不太對,但是我按照Normal,No parity,1 stop,8 bits算出來的確是0x245



      // [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0]

      // Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode

      // 0 1 0 , 0 1 0 0 , 01 01

      // PCLK Level Pulse Disable Generate Normal Normal Interrupt or Polling

      rUCON0 = 0x245; // Control register

      rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0

      delay(10);

      }

      經過以上的折騰,接下來就是kernel的活了.能不能啟動kernel,得看你編譯kernel的水平了.

      這個BOOTLOADER不象blob那樣需要交互信息,使用虛擬地址,總的來說非常簡潔明了.

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 先锋影音资源国产性爱教学 | 亚洲成a人在线一区二区三区 | 一区二区三区四区国产 | 中文文字幕文字幕永久免费 | 亚洲一区二区精品 | 亚洲综合日韩精品欧美国产 |