文章主题
bootloader 是什么?如果你看到了这篇文章,肯定已经知道答案了,所以这里就不赘述了。这篇文章主要是根据韦东山老师的视频,从零开始写一个最简单的 bootloader,每一行代码都是手动输入。虽然直接看一遍视频,也能够理解其中的步骤或者原理,但是根据视频敲一遍之后,印象才是最深刻的。
内容导航
- 测试平台
- 文件目录结构
- 代码详解
- 一些操作指令和流程
测试平台
开发环境:电脑 MAC Pro,安装2个虚拟机 Windows7 和Ubuntu14.04。
- Win7: 用于代码编辑(SourceInsight)和烧录裸板程序(oflash)。需要安装驱动程序:OpenJTAG 驱动,PL2303 USB 转串口驱动。
- Ubuntu14.04:用于交叉编译,交叉编译工具链直接使用光盘里提供的 arm-linux-gcc 即可。
- 文件传送:Win7 与 Ubuntu 之间的文件复制使用 SecureCRT。
开发板:JZ2440-V3,这块板子上已经集成了 USB转串口芯片。
烧录工具:OpenJTAG,连接开发板的 JTAG 接口即可。
测试工具:串口,直接连接 MicroUSB 口到 PC 的 USB 口,串口工具 SecureCRT。
文件目录结构
start.S:第一个启动的程序,完成的功能是:
init.c:串口的初始化和发送数据,NandFlash 的初始化和读取数据,BSS 段初始化。
setup.h:主要是定义了向内核传递参数时所需要的 tag 结构体。
boot.c:main 函数,设置 bootloader 向内核传递的启动参数。
boot.lds:连接脚本。
Makefile:make 编译指令。
代码详解
我比较赞成“代码即注释”这样的编程风格,只要看代码中的注释,就能明白其中的逻辑。所以,这里只贴出代码,不再进行进一步解释说明了。
1、start.S
代码语言:javascript复制#define S3C2440_MPLL_200MHZ ((0x5c<<12) | (0x01<<4) | (0x02))#define MEM_CTL_BASE 0x48000000.text.global _start_start:-
/*1.关看门狗*/ ldr r0,=0x53000000mov r1,#0str r1,[r0]-
/*2.设置时钟*/ ldr r0,=0x4c000014mov r1,#0x03 /* FLCK:HCLK:PCLK = 1:2:4, HDIVN=1, PDIVN=1*/str r1,[r0]-
/*如果 HDIVN 非0, CPU 的总线模式应该从"fast bus mode"改为"asynchronous bus mod"*/ mrc p15,0, r1, c1, c0,0/*读出控制寄存器*/orr r1, r1,#0xc0000000 /* 设置为 "asynchronous bus mod" */mcr p15,0, r1, c1, c0,0/*写入控制寄存器*/-
/*设置 MPLLCON */ ldr r0,=0x4c000004ldr r1,=S3C2440_MPLL_200MHZstr r1,[r0]-
/*3.初始化SDRAM */ ldr r0,=MEM_CTL_BASEadr r1, sdram_configadd r3, r0,#(13*4)1:ldr r2,[r1],#4str r2,[r0],#4cmp r0, r3bne 1b/* b表示向前跳转到标号1的地方*/-
/*4.重定位:把bootloader本身的代码,从flash复制到它的链接地址上去*/ ldr sp,=0x34000000/*把堆栈设置为 SDRAM 的最顶端,因为是向下增长的*/bl nand_init /*初始化 NAND Flash*/mov r0,#0 /* 第一个参数:源地址 */ldr r1,=_start /*第二个参数:目标地址,即链接地址*/ldr r2,=__bss_startsub r2, r2, r1 /*第三个参数:代码长度*/bl copy_code_to_sdram /*调用代码拷贝函数*/bl clean_bss /*调用 bss 段清零函数*/-
/*5.执行 main */ ldr lr,=haltldr pc,=mainhalt:b halt-
/* SDRAM 13个控制器的值*/ 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
2、init.c
代码语言:javascript复制/*NandFlash相关寄存器*/#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 char *)0x56000070))#define GPHUP (*((volatile unsigned char *)0x56000078))/*串口寄存器*/#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 PCLK 50000000 // init.c#define UART_CLK PCLK // UART0 的时钟源设置为 PCLK#define UART_BAUD_RATE 115200 // 波特率#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)#define TXD0READY (1<<2)#define RXD0READY (1)extern void nand_read(unsigned int addr, unsigned char*buf, unsigned int len);/* UART0 初始化*/void uart0_init(void){GPHCON |=0xa0;// GPH2,GPH3 用作 TXD0,RXD0GPHUP =0x0c;// GPH2,GPH3内部上拉ULCON0 =0x03;//8N1UCON0 =0x05;//查询方式,UART时钟源为 PCLKUFCON0 =0x00;//不适用FIFOUMCON0 =0x00;//不使用流控UBRDIV0 = UART_BRD;//波特率115200}/*-
*发送一个字符 -
*/ void putc(unsigned char c){-
/*等待,直到发送缓冲区中的数据已经全部发送出去*/ -
while(!(UTRSTAT0 & TXD0READY)); -
/*向 UTXH0 寄存器中写入数据,UART即自动将它发送出去*/ UTXH0 = c;}unsigned char getc(void){-
/*等待,直到接受缓冲区有数据*/ -
while(!(UTRSTAT0 & RXD0READY)); -
/*直接读取 URXH0 寄存器,即可获得接收到的数据*/ -
return URXH0; }void puts(char *str){int i =0;-
while(str[i]) -
{ putc(str[i]);i ;-
} }/**Checkif boot from NorFlash*Return:1-Yes,0-No*/int isbootFromNorFlash(void){volatile int *p =(volatile int *)0;int val;val =*p;-
*p =0x12345678; -
if(*p ==0x12345678) -
{ -
/* write data success, boot form NandFlash*/ -
*p = val; -
return0; -
} -
else -
{ -
/* write data failed, boot from NorFlash*/ -
return1; -
} }/**InitNandFlashDevice*Return:None*/void nand_init(void){-
/*NandFlash的读写需要满足一定的时序, -
首先查找 NAND FLASH 手册,确定每个信号需要持续的时间,以及两个信号之间需要等待的时间。 -
然后查找2440手册,确定这些时间要设置的是哪个寄存器的哪些bit位,然后通过公式计算出应该设置的值*/ -
#define TACLS 0 -
#define TWRPH0 3 -
#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){unsigned int i;NFCMMD = cmd;-
for(i =0;i <10; i ); }void nand_addr(unsigned int addr){volatile unsigned int i;unsigned int col = addr %2048;unsigned int page = addr /2048;-
/*发出列地址*/ 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)); }char nand_data(void){-
return NFDATA; }/**Read data from NandFlash*Return:None*/void nand_read(unsigned int addr, unsigned char*buf, unsigned int len){unsigned int i =0;int col = addr 48;-
/*1.选中*/ nand_select();-
while(i < len) -
{ -
/*2.发出读命令0x00*/ nand_cmd(0x00);-
/*3.发出地址(分5步发出)*/ nand_addr(addr);-
/*4.发出读命令0x30*/ 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();}/**Copy code from NorFlash or NandFlash to SDRAM*Return:None*/void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len){unsigned int i =0;-
if(isbootFromNorFlash()) -
{ -
while(i < len) -
{ dest[i]= src[i];i ;-
} -
} -
else -
{ nand_read((unsigned int)src, dest, len);-
} }/**Clear BSS Section*Return:None*/void clean_bss(void){extern int __bss_start, __bss_end;int *p =&__bss_start;-
for(; p <&__bss_end; p ) -
*p =0; }
3、setup.h
主要是 tag 数据结构,这里就不贴全部代码了,如果需要可以直接从 uboot 的源码里找到这个文件。
代码语言:javascript复制struct tag {struct tag_header hdr;union {struct tag_core core;struct tag_mem32 mem;struct tag_videotext videotext;struct tag_ramdisk ramdisk;struct tag_initrd initrd;struct tag_serialnr serialnr;struct tag_revision revision;struct tag_videolfb videolfb;struct tag_cmdline cmdline;-
/* -
*Acorn specific -
*/ struct tag_acorn acorn;-
/* -
* DC21285 specific -
*/ struct tag_memclk memclk;-
} u; };
4、boot.c
代码语言:javascript复制#include "setup.h"static struct tag *params;void setup_start_tag(void){-
/* uboot与kernel约定好:在地址0x30000100的地方开始存放参数*/ params =(struct tag *)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_tag(void){params->hdr.tag = ATAG_MEM;params->hdr.size = tag_size(tag_mem32);params->u.mem.start =0x30000000;// SDRAM 开始地址params->u.mem.size =64*1024*1024;// SDRAM 容量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 )!='
