自己动手写bootloader笔记

按照分析的u-boot的启动流程,自己写一个简单的Bootloader。这是参考韦东山老师的视频写的。闲着没啥事玩玩~~~~~~
编写思路:
1、初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND Flash
2、如果Bootloader比较大,要重定位到SDRAM
3、把内核从NAND FLASH读入SDRAM
4、设置要传给内核的参数
5、跳转执行内核

start.S文件:启动流程代码

bootloader启动的第一阶段:
1. 关闭看门狗
2. 设置时钟频率,设置分频系数等
3. 初始化SDRAM, 因为需要快速启动,所以需要将代码从flash上拷贝到内存SDRAM中启动
4. 代码重定向 (注意nor启动和nand启动的不同点)
5. 执行main函数,跳转到启动的第二阶段

#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
#define MEM_CTL_BASE 0x48000000

.text
.global _start
_start:
/* 1. 关看门狗 */
    ldr r0, =0x53000000
    mov r1, #0
    str r1, [r0]

/* 2. 设置时钟 */
    ldr r0, =0x4c000014
    // mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
    mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
    str r1, [r0]

    /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
    mrc p15, 0, r1, c1, c0, 0       /* 读出控制寄存器 */
    orr r1, r1, #0xc0000000         /* 设置为“asynchronous bus mode” */
    mcr p15, 0, r1, c1, c0, 0       /* 写入控制寄存器 */

    /* MPLLCON = S3C2440_MPLL_200MHZ */
    ldr r0, =0x4c000004
    ldr r1, =S3C2440_MPLL_400MHZ
    str r1, [r0]

    /* 启动ICACHE, 开启ICACHE将加快启动速度 */
    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


/* 3. 初始化SDRAM */
    ldr r0, =MEM_CTL_BASE
    adr r1, sdram_config /* sdram_config的当前地址 */
    add r3, r0, #(13*4)
1:
    ldr r2, [r1], #4
    str r2, [r0], #4
    cmp r0, r3
    bne 1b

/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */
    ldr sp, =0x34000000 /* 将SP放在SDRAM的最高地址处,堆栈式满递减堆栈。在调用C函数之前,必须设置好堆栈 */

    bl nand_init //运行init.c的nand_init(void)

    mov r0, #0
    ldr r1, =_start             /* _start是起始地址,是链接地址, 由链接文件.lds 指定 */
    ldr r2, =__bss_start        /* __bss_start 同样是链接地址,在链接文件中指定 */
    sub r2, r2, r1

/*
* copy_code_to_sdram: 被调用的C函数,
* 第一个参数是R0:表示拷贝数据的源地址,这里从0地址(源地址)处拷贝数据到链接地址(真正运行的地址,也就是目的地址)
* 第二个参数是R1:表示拷贝数据的目的地址,就是链接地址
* 第三个参数是R2:表示拷贝数据的长度 = bss段起始地址 - 链接的起始地址。(都在链接文件中定义。)
*/ 
    bl copy_code_to_sdram  //运行init.c的copy_code_to_sdram


/*
* 由上面的拷贝代码知道,在bss段中的数据并没有拷贝到内存当中去,这是因为这些数据初始化都是0,所以只需要在初始化的时候,显示的把bss段清零,就OK了。
*/
    bl clear_bss

    
/* 5. 执行main */
    ldr lr, =halt
    ldr pc, =main  //跳到boot.c的main执行
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

链接文件 boot.lds:

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

Makefile:

CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump

CFLAGS         := -Wall -O2
CPPFLAGS     := -nostdinc -nostdlib -fno-builtin

objs := start.o init.o boot.o

boot.bin: $(objs)
    ${LD} -Tboot.lds -o boot.elf $^
    ${OBJCOPY} -O binary -S boot.elf $@
    ${OBJDUMP} -D -m arm boot.elf > boot.dis
    
%.o:%.c
    ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

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

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

boot.c

#include "setup.h"

extern void uart0_init(void);
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
extern void puts(char *str);
extern void puthex(unsigned int val);


static struct tag *params;

void setup_start_tag(void)
{
    params = (struct tag *)0x30000100; //这里设置了内核参数放置的位置为:0x30000100

    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);
}

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;   // 表示内存的大小,这里为64MB
    
    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;
    
    params->hdr.tag = ATAG_CMDLINE;
    params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;  //注意:这里要四字节对齐方式

    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 flash的地址0x60064处拷贝大小为0x200000的代码到内存地址为0x30008000处。即:从nand flash上拷贝内核到SDRAM中。 
    nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
             // 0x30008000为内核需要运行的开始地址(就是链接地址,在SDRAM当中),0x200000为整个内核的长度大小。
             // uImage = 64bytes + zImage; zImage为真正的内核。uImage存在0x60000处。
             // 所以zImage的起始地址为:0x60000 + 64 

    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); // 向内核启动的函数,传递了三个参数,
                                   // 第一个参数为0
                                   // 第二个参数为362, 为机器号
            // 第三个参数为0x30000100:这个是内核需要的启动参数的起始地址。内核启动需要从这个地方获取所需的启动参                数,比如内存的起始地址,大小等等。
 
    /* 
     * mov r0, #0
     * ldr r1, =362
     * ldr r2, =0x30000100
     * mov pc, #0x30008000 
     */

    puts("Error!\n\r");
    /* 如果一切正常, 不会执行到这里 */

    return -1;
}

 还有个setup.h文件

init:

/* NAND FLASH控制器 */
#define NFCONF (*((volatile unsigned long *)0x4E000000))
#define NFCONT (*((volatile unsigned long *)0x4E000004))
#define NFCMMD (*((volatile unsigned char *)0x4E000008))
#define NFADDR (*((volatile unsigned char *)0x4E00000C))
#define NFDATA (*((volatile unsigned char *)0x4E000010))
#define NFSTAT (*((volatile unsigned char *)0x4E000020))
 
/* 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)
 
 
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
 
 
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);
	}
}
 
void clear_bss(void)
{
	extern int __bss_start, __bss_end;
	int *p = &__bss_start;
	
	for (; p < &__bss_end; p++)
		*p = 0;
}
 
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);	
}
 
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();
}
 
#define PCLK            50000000    // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK        PCLK        //  UART0的时钟源设为PCLK
#define UART_BAUD_RATE  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寄存器中写入数据,UART即自动将它发送出去 */
    UTXH0 = c;
}
 
void puts(char *str)
{
	int i = 0;
	while (str[i])
	{
		putc(str[i]);
		i++;
	}
}
 
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);
		
	}
	
}

start.S调用init.c,后调用boot.c
编译完成,生成boot.bin

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值