自己寫個一簡單的bootloader

步驟:
1、關看門狗
2、設置棧,調用C函數進行其他初始化,但從定位代碼前的代碼,必須保證位置無關性。
3、初始化時鐘
4、初始化存儲控制器,以使用SDRAM
5、設置存儲控制器後,SDRAM可用了,重新設置棧指向SDRAM最高處。
6、初始化串口
7、初始化nand flash
8、重定位代碼
9、清楚bss段 //參考uboot源碼
    //步驟123456789都可以參考之前寫過的裸板程序。沒什麼好說的。
10、跳轉到重定位後的main()函數中執行。//在boot.c文件中
   10.1設置各種啓動參數
   setup_start_tag();
   setup_memory_tags();
   setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
   setup_end_tag();
   10.2調用theKernel,跳轉到內核頭部執行
   

沒什麼好說的,直接上代碼。(大部分代碼都是參考百問網的,我添加了些註釋,非常方便理解)

start.S文件:

.text
.global _start
_start:

/* 1. 關看門狗 */

	 ldr     r0, =0x53000000     @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str     r1, [r0]            @ 寫入0,禁止WATCHDOG,否則CPU會不斷重啓
	ldr sp,=4096
 bl  clock_init          @ 設置MPLL,改變FCLK、HCLK、PCLK
    bl  memsetup            @ 設置存儲控制器以使用SDRAM
  
	   

/* 4. 重定位 : 把bootloader本身的代碼從flash(nand 或者 nor flash)複製到它的鏈接地址去 */
	ldr sp, =0x34000000
	bl uart0_init	/*sdram基地址0x30000000,64Mb 是0x34000000,指向最高 */
	bl nand_init		/* nand 啓動的話,要在前4K代碼裏面實現這個功能:把uboot全部從nand flash 複製到SDRAM */
		/* 但是,nand啓動的話,這個uboot被加載在哪裏?被加載到nand的起始地址0,不然自動複製前4k就沒意義了 */
	mov r0, #0		/* uboot 燒到nor flash 0地址 */
	ldr r1, =_start  /* 對nand flash,cpu發出的0地址是對nand flash的0地址讀寫數據,其相關寄存器纔對CPU統一編址 */
	ldr r2, =__bss_start	/* 這裏是__bss_start,不是__bss_end */
			/* uboot 被鏈接在SDRAM很高的位置,最後512k,0x33f80000,0x30000000要放內核 */
	sub r2, r2, r1	/* 讀到哪裏去?讀到對CPU來說的鏈接地址去 */
	bl copy_code_to_sdram
	bl clear_bss
	
/* 5. 執行main */
	ldr lr, =halt
	ldr pc, =main		//main()函數在boot.c裏面
halt:
	b halt
init.c文件:(主要提供一些初始化操作)

#include "serial.h"

/* NAND FLASH控制器 */
/* NAND Flash registers */
#define NFCONF              (*(volatile unsigned int  *)0x4e000000)
#define NFCMD               (*(volatile unsigned char *)0x4e000004)
#define NFADDR              (*(volatile unsigned char *)0x4e000008)
#define NFDATA              (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT              (*(volatile unsigned char *)0x4e000010)

#define NFCONT (*((volatile unsigned long *)0x4E000004))
#define NFCMMD (*((volatile unsigned char *)0x4E000008))


void clock_init(void);
void memsetup(void);
void copy_steppingstone_to_sdram(void);
int isBootFromNorFlash(void);
void clear_bss(void);
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);


/*
 * 對於MPLLCON寄存器,[19:12]爲MDIV,[9:4]爲PDIV,[1:0]爲SDIV
 * 有如下計算公式:
 *  S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)
 *  S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
 *  其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
 * 對於本開發板,Fin = 12MHz
 * 設置CLKDIVN,令分頻比爲:FCLK:HCLK:PCLK=1:2:4,
 * FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
 */
void clock_init(void)
{
	
    #define S3C2410_MPLL_200MHZ     ((0x5c<<12)|(0x04<<4)|(0x00))
    #define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
    
#define	MPLLCON		(*(volatile unsigned long *)0x4c000004)
#define	CLKDIVN		(*(volatile unsigned long *)0x4c000014)
#define GSTATUS1    (*(volatile unsigned long *)0x560000B0) 
    // LOCKTIME = 0x00ffffff;   // 使用默認值即可
    CLKDIVN  = 0x03;            // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1

    /* 如果HDIVN非0,CPU的總線模式應該從“fast bus mode”變爲“asynchronous bus mode” */
__asm__(
    "mrc    p15, 0, r1, c1, c0, 0\n"        /* 讀出控制寄存器 */ 
    "orr    r1, r1, #0xc0000000\n"          /* 設置爲“asynchronous bus mode” */
    "mcr    p15, 0, r1, c1, c0, 0\n"        /* 寫入控制寄存器 */
    );

    /* 判斷是S3C2410還是S3C2440 */
    if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
    {
        MPLLCON = S3C2410_MPLL_200MHZ;  /* 現在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
    }
    else
    {
        MPLLCON = S3C2440_MPLL_200MHZ;  /* 現在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
    }       
}

/*
 * 設置存儲控制器以使用SDRAM
 */
void memsetup(void)
{
	#define MEM_CTL_BASE    0x48000000
    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;

    /* 這個函數之所以這樣賦值,而不是像前面的實驗(比如mmu實驗)那樣將配置值
     * 寫在數組中,是因爲要生成”位置無關的代碼”,使得這個函數可以在被複制到
     * SDRAM之前就可以在steppingstone中運行
     */
    /* 存儲控制器13個寄存器的值 */
    p[0] = 0x22011110;     //BWSCON
    p[1] = 0x00000700;     //BANKCON0
    p[2] = 0x00000700;     //BANKCON1
    p[3] = 0x00000700;     //BANKCON2
    p[4] = 0x00000700;     //BANKCON3  
    p[5] = 0x00000700;     //BANKCON4
    p[6] = 0x00000700;     //BANKCON5
    p[7] = 0x00018005;     //BANKCON6
    p[8] = 0x00018005;     //BANKCON7
    
                                            /* REFRESH,
                                             * HCLK=12MHz:  0x008C07A3,
                                             * HCLK=100MHz: 0x008C04F4
                                             */ 
    p[9]  = 0x008C04F4;
    p[10] = 0x000000B1;     //BANKSIZE
    p[11] = 0x00000030;     //MRSRB6
    p[12] = 0x00000030;     //MRSRB7
    

}

int isBootFromNorFlash(void)
{
	volatile int *p = (volatile int *)0;
	int val;
	val = *p;
	*p = 0x12345678;
	if (*p == 0x12345678)
	{
		/* 寫成功, 是nand啓動 */
		*p = val;
		return 0;
	}
	else
	{
		/* NOR不能像內存一樣寫 */
		return 1;
	}
}

void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{	
	int i = 0;
	
	/* 如果是NOR啓動 */
	if (isBootFromNorFlash())
	{
		while (i < len)
		{
			dest[i] = src[i];
			i++;
		}
	}
	else
	{
		//nand_init();
		nand_read((unsigned int)src, dest, len);
	}
	puts("copy code done!\n\r");

}


void nand_init(void)
{
#define TACLS   0
#define TWRPH0  1
#define TWRPH1  0
	/* 設置時序 */
	NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
	/* 使能NAND Flash控制器, 初始化ECC, 禁止片選 */
	NFCONT = (1<<4)|(1<<1)|(1<<0);	
        puts("init nand done!\n\r");

}

void nand_select(void)
{
	NFCONT &= ~(1<<1);	
}

void nand_deselect(void)
{
	NFCONT |= (1<<1);	
}

void nand_cmd(unsigned char cmd)
{
	volatile int i;
	NFCMMD = cmd;
	for (i = 0; i < 10; i++);
}

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

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

void nand_wait_ready(void)
{
	while (!(NFSTAT & 1));
}

unsigned char nand_data(void)
{
	return NFDATA;
}

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	int col = addr % 2048;
	int i = 0;
		
	/* 1. 選中 */
	nand_select();

	while (i < len)
	{
		/* 2. 發出讀命令00h */
		nand_cmd(0x00);

		/* 3. 發出地址(分5步發出) */
		nand_addr(addr);

		/* 4. 發出讀命令30h */
		nand_cmd(0x30);

		/* 5. 判斷狀態 */
		nand_wait_ready();

		/* 6. 讀數據 */
		for (; (col < 2048) && (i < len); col++)
		{
			buf[i] = nand_data();
			i++;
			addr++;
		}
		
		col = 0;
	}

	/* 7. 取消選中 */		
	nand_deselect();
}



void clear_bss(void)
{
	extern int __bss_start, __bss_end;	/* c中用到這樣的地址要extern,彙編中不用extern */
	int *p = &__bss_start;
	for (; p < &__bss_end; p++)
		*p = 0;
}
boot.c文件:(設置tags,調用theKernel())

#include "setup.h"
#include "serial.h"

extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);

static struct tag *params;

void setup_start_tag(void)
{
	params = (struct tag *)0x30000100;	/* 內核如何知道我uboot把這些參數存放在0x30000100? */
			/* thekernel 指向真正的uimage(不含頭部,頭部不被複制)0x30008000,何故不放在0x30000000? */
	params->hdr.tag = ATAG_CORE;		/* theKernel(0, 362, 0x30000100);  告訴內核參數位置 */
	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);
}

void setup_memory_tags(void)
{
	params->hdr.tag = ATAG_MEM;
	params->hdr.size = tag_size (tag_mem32);
	
	params->u.mem.start = 0x30000000;
	params->u.mem.size  = 64*1024*1024;
	
	params = tag_next (params);
}

int strlen(char *str)
{
	int i = 0;
	while (str[i])
	{
		i++;
	}
	return i;
}

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

void setup_commandline_tag(char *cmdline)	/* 這個是何故這樣設置?他的設置方法是怎麼樣的? */
{
	int len = strlen(cmdline) + 1;		/* 各參數4字節對齊,包含字符串參數 */
						/* 包含bootargs 的整一串字符串,而不是一個一個的存放 */
	/* 這樣調用,各個命令一起:setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"); */
	params->hdr.tag  = ATAG_CMDLINE;
	params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2; /* 加3再除4 即可取整 */

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

	params = tag_next (params);
}

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


int main(void)
{
	void (*theKernel)(int zero, int arch, unsigned int params);
	volatile unsigned int *p = (volatile unsigned int *)0x30008000;

	/* 0. 幫內核設置串口: 內核啓動的開始部分會從串口打印一些信息,但是內核一開始沒有初始化串口 */
	/* uart0_init(); */
	
	/* 1. 從NAND FLASH裏把內核讀入內存 */
	puts("Copy kernel from nand\n\r");
	nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
	
	puthex(0x1234ABCD);
	puts("\n\r");
	puthex(*p);
	puts("\n\r");

	/* 2. 設置參數 */
	puts("Set boot params\n\r");
	setup_start_tag();
	setup_memory_tags();
	setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
	setup_end_tag();

	/* 3. 跳轉執行 */
	puts("Boot kernel\n\r");
	theKernel = (void (*)(int, int, unsigned int))0x30008000;
	theKernel(0, 362, 0x30000100);  
	/* theKernel(0, 362, 0x30000100);  相當於下面的彙編:
	 *  mov r0, #0
	 *  ldr r1, =362
	 *  ldr r2, =0x30000100
	 *  mov pc, #0x30008000 
	 */

	puts("Error!\n\r");
	/* 如果一切正常, 不會執行到這裏 */

	return -1;
}
     啓動時,事先要使用其他辦法把uImage燒寫到nand的60000中,能否掛載根文件系統是內核與bootloader設置的啓動參數的問題,這裏不作討論。

     對啓動過程不理解,可以參考我這篇文章:U-boot啓動內核流程的詳細分析點擊打開鏈接




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章