自己写个一简单的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启动内核流程的详细分析点击打开链接




你可能感兴趣的:(U-Boot与内核,裸板)