U-BOOT詳解2.從0編寫uboot


一、硬件初始化和制作鏈接腳本lds

1.1、目標

第一階段:

  • 關看門狗
  • 設置時鍾
  • 初始化SDRAM(初始化寄存器以及清除bss段)
  • 重定位(將nand/nor中的代碼COPY到鏈接地址上,需要初始化nandflash,讀flash)
  • 執行main

進入第二階段:

  • 寫main函數,在main()中設置要傳給內核的參數,然后跳轉內核基地址處
  • 制作uboot.lds鏈接腳本

1.2、創建工程並編寫start.S

1)創建個名為"my_bootloader"根目錄,方便編寫uboot
2)新建my_bootloader/si目錄,創建source insight工程
3)新建my_bootloader/start.S (后綴名必須是大寫的S,或者后面編譯會報錯)
  編寫start.S (第一階段初始硬件文件):

/*  看門狗寄存器            */
#define WTCON      0x53000000
 
/*  2.時鍾寄存器            */
#define CLKDIVN    0x4C000014        //設置FCLK,HCLK,PCLK比例
#define MPLLCON   0x4C000004          //設置FCLK頻率
#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))  //設置FCLK=200MHZ
#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))  //設置FCLK=400MHZ
 
/*  3.bank寄存器       */
#define BWSCON      0x48000000
 
.text                                /*設置代碼段*/
.global   _start                  /*定義全局變量,要被連接腳本用到*/
_start:                           /*_start跳轉到這里實現硬件初始化*/
 
    /*  1.關看門狗            */
       ldr r0,=WTCON
       mov r1,#0
       str r1,[r0]
        
    /*  2.設置時鍾(必須設為異步總線模式)  */
        
       ldr r0,=CLKDIVN
       mov r1,#3                                  /*FCLK:HCLK:PCLK=1:2:4*/
       str r1,[r0]        
       mrc  p15, 0, r1, c1, c0                 /* 讀出控制寄存器 */   
       orr   r1, r1, #0xc0000000         /* 設置為“asynchronous bus mode” */
       mcr    p15, 0, r1, c1, c0, 0             /* 寫入控制寄存器 */      
       ldr r0,=MPLLCON
       ldr r1,=S3C2440_MPLL_200MHZ      //使用復雜的數不能用mov,需要用ldr
       str r1,[r0]
        
    /*  3.初始化SDRAM       */
       ldr r0,=BWSCON                   //r0=SDRAM寄存器基地址
       adr r1,SDRAM_CONFIG           //使用adr相對跳轉,因為SDRAM未初始化
       add r2,r0,#(13*4)               //r2等於 SDRAM尾地址+4
 
0:
       ldr r3,[r1],#4                      //r3=r1里的    內容, &r1+=4;
       str r3,[r0],#4                      //*r0=r3,&r0+=4
       cmp r0,r2
       bne 0b                               //(ne:no equal   b:bank)   若r0!=r2,跳轉到后面的0處
 
 /*  4.重定位              */
    ldr sp,=0x34000000                     //64Msdram,所以設置棧SP=0x34000000,避免堆棧溢出
    mov r0,#0                           //r0->src
    ldr r1,=_start                     //r1->dest
    ldr r2,=__bss_start            //r2->len->__bss_start-_start
    sub r2,r2,r1
    bl copy_code_to_sdram          //復制代碼到SDRAM連接地址dest上
    bl clear_bss                         //清除bss段 
     
   /*  5.執行main              */
    ldr lr,=halt                         //執行一個子程序調用返回時,需要先將返回地址賦給lr鏈接寄存器
    ldr pc,=main                              //初始化SDRAM后,可以使用pc 絕對跳轉到SDRAM地址上
    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

1.3、編寫my_bootloader/init.c

  新建my_bootloader/init.c,用於重定位,bss段清除,初始化NandFlash等
  在重定位函數中的nand驅動在(http://www.cnblogs.com/lifexy/p/7097695.html)這篇帖子有詳細介紹,這里就不描述.
  1)編寫copy_code_tosdram() 重定位函數

void copy_code_to_sdram(unsigned char *src,unsigned char *dest,unsigned int len)            //復制代碼到SDRAM連接地址dest上
{
	unsigned int i;
	/*判斷nor啟動還是nand啟動*/
    if(is_boot_from_norflash())                 //nor啟動,直接復制
    {
		for(i=0;i<len;i++)
		{
			dest[i]=src[i];
		}
    }
    else
    {
		nand_init();
		nand_read((unsigned int)src,dest,len);
    }
}

  2)編寫isBootFramNorFlash()函數,來判斷nand啟動還是nor啟動

/*判斷nor啟動還是nand啟動*/
unsigned char is_boot_from_norflash(void)
{
	volatile unsigned int *p=(volatile unsigned int *)0;
	unsigned int i;
	i=*p;
	*p=0x12345678;
	if(*p==0x12345678)    //nand
	{
		*p=i;
		return 0;
	}
	else                    //nor
	{
		*p=i;
		return 1;
	}
}

  3)編寫clear_bss()函數

void clear_bss(void)  //清除BSS段
{
	extern int __bss_start,__bss_end;
	int  *p=&__bss_start;
	for( ;p<&__bss_end;p++)
	{
		*p=0;
	}
}

1.4、編寫鏈接腳本uboot.lds

  參考硬件實驗里的uart.lds和u-boot-1.1.6里的u-boot.lds

SECTIONS {
    . = 0x33f80000; //0地址里存放0X33F80000
    . = ALIGN(4);
 
    .text : { *(.text) }
    . = ALIGN(4);
 
    .rodata : { *(.rodata)  }
    . = ALIGN(4);
 
    .data : {   *(.data)    }
    . = ALIGN(4);
 
    __bss_start = .;
    .bss : { *(.bss) *(COMMON) }
    __bss_end = .;
}

  其中0x33f80000就是鏈接地址首地址,共512K空間存放bootloader
  定義__bss_start__bss_end符號,是用來程序開始之前將這些未定義的變量清0,節省內存且bss_start-0x33f80000就等於代碼的大小(copy_code_tosdram函數中len值)

二、啟動內核和制作Makefile

2.1、目標

1)添加頭文件setup.h和serial.h
2)寫main函數
  幫內核設置串口0, (內核啟動會打印出啟動信息)
  把內核讀入到SDRAM
  設置參數(參考u-boot-1.1.6 /lib_arm/armlinux.C中do_bootm_linux()函數)
  跳轉運行內核(參考u-boot-1.1.6/lib_arm/armlinux.C中do_bootm_linux()函數)
3)寫tag參數函數
  setup_start_tag (void);
  setup_memory_tags(void);
  setup_commandline_tag (char *cmdline);
  setup_end_tag (void);
4)寫makefile文件

2.2、添加頭文件setup.h和serial.h

1)將lcd裸板程序中串口uart0初始化文件serial.c復制到my_bootloader目錄中.並修改serial.c
2)因為TAG結構體定義是存在u-boot-1.1.6/include/asm-arm/setup.h中,所以設置TAG參數需要用到這個文件,將setup.h復制到my_bootloader目錄中.
3)修改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)

添加以下代碼

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

2.3、編輯my_bootloader/boot.c

  新建my_bootloader/boot.c,用於存放main函數(main:由start.S跳轉過來的).
  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.4、創建TAG參數函數

設置tag參數函數代碼如下

#include “setup.h”
static struct tag *params;               //定義個tag結構體變量params指針
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;
}

2.5、寫makefile文件

首先將lcd裸板程序里的makefile復制到my_bootloader目錄中,並修改.

備注:在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
CFLAGS           := -Wall -O2         //GCC編譯參數,-Wall:顯示所有錯誤和警告, -O2:采用2級編譯優化
CPPFLAGS     := -nostdinc -fno-builtin       
//添加頭文件參數,-nostdinc忽略缺省目錄, -fno-builtin不連接系統標准啟動文件和標准庫文件(表示不用自帶的strlen()等庫函數)
objs := start.o init.o boot.o         //定義objs變量,包含生成boot.bin目標文件需要的依賴文件
boot.bin: $(objs)                      //執行生成目標文件,首先是先滿足objs所有依賴文件都擁有,才執行
         ${LD} -Tuboot.lds -o boot_elf $^
         ${OBJCOPY} -O binary -S boot_elf $@
         ${OBJDUMP} -D -m arm boot_elf > boot.dis
%.o:%.c                                //%通配符。生成xxx.o文件先要找到xxx.c文件
         ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<   //-c編譯不連接。$@表示目標文件   $<表示第一個依賴文件
%.o:%.S
         ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
         rm -f *.bin *.elf *.dis *.o

三、編譯測試以及改進

3.1、編譯測試

1)將寫好的uboot復制到linux下面
2)make編譯,然后將錯誤的地方修改,生成boot.bin(編譯出錯的解決方案:http://www.cnblogs.com/lifexy/p/7326172.html)
3)通過make生成的反匯編來查看代碼是否正確
4)通過oflash燒寫到板子nand flash上
5)查看串口是否數據打印
  發現串口無數據,發現兩處錯誤:
  5.1)在init.C中define定義 沒有加大括號,沒有定義volatile型

例如:
#define NFCONF       *((unsigned long *)0x4E000000)  
需要改為: #define NFCONF      (*((volatile unsigned long *)0x4E000000) )

  5.2)在init.C中nand_read_addr()函數出錯,代碼如下:

void nand_read_addr(unsigned int addr)
{
   volatile int i;
   NFADDR=( addr>>0)&0xff;         //A7~A0        
   for(i=0;i<10;i++);

   NFADDR=( addr>>8)&0x0f;         //A11~A8
   for(i=0;i<10;i++);

   NFADDR=( addr>>12)&0xff;         //A19~A12
   for(i=0;i<10;i++);

   NFADDR=( addr>>20)&0xff;         //A27~A20
   for(i=0;i<10;i++);

   NFADDR=( addr>>28)&0xff;         //A28
   for(i=0;i<10;i++);  
}

  上面之所以錯是因為nand flash是2048B一頁,這里的寫的一頁是A11~A0,其值=4096,已經屬於兩頁大小了
  應該改為:

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

   NFADDR=(col>>0)&0xff;         //A7~A0
   for(i=0;i<10;i++);

   NFADDR=(col>>8)&0x0f;         //A10~A8
   for(i=0;i<10;i++);

   NFADDR=(page>>0)&0xff;         //A18~A11
   for(i=0;i<10;i++); 

   NFADDR=(page>>8)&0xff;         //A26~A19
   for(i=0;i<10;i++);

   NFADDR=(page>>16)&0xff;         //A27
   for(i=0;i<10;i++);   
}

3.2、優化改進(加快內核啟動時間)

3.2.1、提高CPU頻率

  將CPU頻率設為最大值400MHZ(內核啟動時間7S變為6S,因為HCLK和PCLK頻率沒有改變)
  然后分頻系數FCLK:HCLK:PCLK需要設置為1:4:8
  因為HCLK最高133MHZ,這里需要設置為100MHZ
  PCLK最高50MHZ,這里需要設置為50HZ
  如下圖所示,得出 CLKDIVN寄存器需要等於0X5即可
  通過下圖看出,提高CPU頻率需要設置MPLLCON中MDIV、PDIV、SDIV
  通過下圖得出,取400MHZ時,設置MDIV為0X5C,PDIV為0x1,SDIV為0x1
所以改進代碼如下:

#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))  //設置FCLK=400MHZ
/*    2.設置時鍾(必須設為異步總線模式)  */
   ldr r0,=CLKDIVN
   mov r1,#5                                  /*FCLK:HCLK:PCLK=1:4:8*/
   str r1,[r0]
   mrc p15, 0, r1, c1, c0                 /* 讀出控制寄存器 */
   orr  r1, r1, #0xc0000000               /* 設置為“asynchronous bus mode” */
   mcr   p15, 0, r1, c1, c0, 0               /* 寫入控制寄存器 */

   ldr r0,=MPLLCON
   ldr r1,= S3C2440_MPLL_400MHZ      //使用復雜的數不能用mov,需要用ldr
   str r1,[r0]

3.2.2、開啟ICAHE(內核啟動時間6S變為1.5S)

CAHE介紹:
  通過高速緩存存儲器來加快對內存的數據訪問,在CAHE中有ICAHE(指令緩存)和DCAHE(數據緩存)
  ICAHE: 指令緩存,用來存放執行這些數據的指令
  DCAHE:用來存放數據,需要開啟MMU才能開啟DCAHE
  在沒開啟ICAHE之前,CPU讀取SDRAM地址數據時,每次都需要先訪問一次地址值,在讀數據.
  當開了ICAHE后,第一次讀取SDRAM地址數據時,ICAHE發現緩存里沒有這個地址數據,然后將SDRAM中需要讀取的那部分一大塊內存數據都復制在緩存中,后面陸續讀取數據就不會再訪問SDRAM了,直到CPU沒有找到地址數據后ICAHE再從SDRAM中重新復制

通過CP15協處理器來開啟ICAHE
   (CP15協處理器操作用法: http://www.cnblogs.com/lifexy/p/7203786.html)
  從上面鏈接中可以找到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

參考鏈接:
https://www.cnblogs.com/lifexy/p/7337475.html


免責聲明!

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



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