首先我们必须要知道,一开始我们的开发板上电的时候, 如果我们的板子是从norflash启动的,那么硬件会从nandflash拷贝其前4k的代码到内部RAM中, (这也是为什么我们的bootloader第一阶段需要在4k内),如果是norflash启动,那也是从norflash把前4k代码复制到内部RAM中
我们知道mini2440有个看门狗,如果说我们不能够定时去喂狗, 那么它会在超时的时候自动重启(硬件决定的),所以我们的bootloader的第一步就是
① 关闭看门狗
2440在启动的时候他是直接以外部晶振直接作为系统时钟,所以系统运行在12MHz的,这个频率实在是太小了, 所以我们为了让我们的板子运行速度加快,那么我们第二步就是
② 修改板子的时钟频率
在接下来我们想想要做什么呢?我们是不是想要启动内核,那启动内核之前是不是需要先把内核从nandflash(内核是放在nandflash的0x60000的)拷贝到内存SDRAM中,而这个SDRAM(64M)在进行读写前是需要你先去初始化他的一些存储单元才能够正常的进行读写的!!!!所以我们的第三部就是
③ 初始化SDRAM
我们把内核拷贝到SDRAM前,得先初始化nandflash, 才能够写nandflash,所以:
④ 初始化nandflash
接下来由于我们后面的操作是使用了c语言,故我们先要设置栈sp
⑤ 设置栈sp
那么我们接下来是不是就是去把内核拷贝到SDRAM中呢?别急,我们要知道我们的bootloader目前只有前4k的代码被拷贝到内部RAM中,如果说我们的bootloader超过4k的话那么我们的bootloader就很有可能会出问题, 所以我们接下来要做的就是拷贝bootloader到SDRAM中,再跳过去运行, 这叫做:
⑥ 重定位
重定位结束后,我们需要先清除bss段,也就是赋值为0, 具体什么是bss段这里就不解释了
⑦ 清bss段
接下来进入bootloader第二阶段
接下来我们终于到了把内核从nandflash中拷贝到SDRAM中,这就涉及到读nandflash了,而nandflash读的时候呢,需要我们先去
⑧ 为了调试方便我们在这里初始化串口, 让串口能够为我们打印一下提示信息
⑨ 把内核从nandflash拷贝到SDRAM中
⑩ 设置好传递给内核的参数 调用内核
接下来讲解具体的代码, 首先是 start.s 这个是第一阶段的文件.
- .text
- .global _start
- _start:
- /*①关看门狗*/
- ldr r0 ,= 0x53000000
- mov r1 , #0
- str r1 , [r0]
- /* ②设置时钟 ,因为他本来的时钟频率为12mhz 太低*/
- ldr r0 ,= 0x4c000014 /*CLKDIVN*/
- mov r1 , #0x5 /*#5 F:H:P = 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_400MHZ */
- ldr r0 ,= 0x4c000004
- ldr r1 ,= ((0x7f<<12)|(0x2<<4)|0x1) /* 设置为400mhz*/
- str r1,[r0]
- /* 启动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
- /* ③初始化SDRAM*/
- ldr r0 ,= 0x48000000
- adr r1 ,sdram_config
- add r3 , r0 ,#(13*4)
- 0:
- ldr r2 , [r1] ,#4
- str r2 , [r0] ,#4
- cmp r0,r3
- bne 0b
- /* led 此步骤可以不要 , 只是一开始用于调试, 点灯看看程序运行状况 */
- ldr r0 ,= 0x56000010
- ldr r1 ,= (0x55<<10)
- str r1,[r0]
- /* ④ 设置栈 */
- ldr sp,=0x34000000 /*0x34000000这是sdram的结束地址,栈的设置 很重要 , 设置错误 很容易导致系统重启*/
- bl uart0_init /*初始化串口, 之所以把这个放到这里, 主要原因是为了方便调试*/
- /*⑤初始化nandflash*/
- bl nand_init
- /*⑥重定位*/
- mov r0 , #0
- ldr r1 ,= 0x33f80000 @_start @0x33f80000
- ldr r2 ,= __bss_start
- @sub r2 , r2 , r1
- bl copy_code_to_sdram /*上面的r0-r2是参数*/
- bl clear_bss /*清 bss 段*/
- /*goto main*/
- ldr lr ,= halt
- ldr pc ,= Main
- bl led_on
- halt:
- b halt
- sdram_config:
- .long 0x22011110
- .long 0x00000700
- .long 0x00000700
- .long 0x00000700
- .long 0x00000700
- .long 0x00000700
- .long 0x00000700
- .long 0x00018005
- .long 0x00018005
- .long 0x008C04F4
- .long 0x000000B1
- .long 0x00000030
- .long 0x00000030
首先看我们的uart_init()初始化串口:
- void uart0_init(void)
- {
- GPHCON = (GPHCON&(~(0xf<<4)))|(0xa<<4);// GPH2,GPH3用作TXD0,RXD0
- UFCON0 = 0x00; //不使用FIFO
- UMCON0 = 0x00; //不使用自动流控制
- ULCON0 = 0x03; //不采用红外线传输模式,无奇偶校验位,1个停止位,8个数据位
- UCON0 = 0x05; //发送中断为电平方式,接收中断为边沿方式,禁止超时中断,允许产生错误状态中断,禁止回送模式,禁止中止信号 //信号,传输模式为中断请求模式,接收模式也为中断请求模式。
- UBRDIV0=((int)(50000000/(UART_BAUD_RATE*16))-1); //根据波特率计算UBRDIV0的值
- }
串口设置好后, 很明显我们需要写串口输出的函数:
- /* 发送一个字符 */
- void _putc(char c)
- {
- /* 等待,直到发送缓冲区中的数据已经全部发送出去 */
- while (!(UTRSTAT0 & TXD0READY));
- /* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
- UTXH0 = c;
- }
- /*发送字符串*/
- void _puts(char *str)
- {
- while(*str){
- _putc(*str++);
- }
- }
先贴下宏定义:
- #define uchar unsigned char
- #define uint unsigned int
- /* 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 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)
- #define u32 unsigned long
- #define TXD0READY (1<<2)
接下来我们看看nandflash的初始化函数nand_init()
- void nand_init(){
- #define TACLS 0
- #define TWRPH0 1 // hclk * (twrph0+1) = 0.1ns * (twrph0 + 1)>=12
- #define TWRPH1 0
- /* 设置时序 上面的三个宏的设置请参考数据手册, 具体说明:<a href="http://hi.baidu.com/kqalowgkkceqvys/item/f6406fdb731fdef593a97404">http://hi.baidu.com/kqalowgkkceqvys/item/f6406fdb731fdef593a97404</a>*/
- NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4 );
- /* 使能nand flash 控制器, 初始化ECC, 禁止片选 */
- NFCONT = (1<<4)|(1<<1)|(1<<0);
- }
接下来到了copy_code_to_sdram(), 也就是重定位
- void copy_code_to_sdram(uchar* src_addr,uchar* dest,uint len){
- /* 判断从哪里启动 */
- uint i = 0;
- _puts("copy_code_to_sdram\n\r");
- if(isBootFromNorFlash()){// 如果是从norflash启动, 直接拷贝
- _puts("NorFlash\n\r");
- while(i<len){
- *dest++ = *src_addr++;
- i++;
- }
- }else{
- _puts("Nand Flash\n\r");
- nand_read((uint)src_addr,dest,len);//下面解释
- }
- _puts("copy_code_to_sdram end .................\n\r");
- }
nandflash的读操作是要参考数据手册的,
- 片选
- 发送00h命令
- 发送地址5周期
- 发送读命令 30h
- 判断状态
- 读..
- 取消片选
- void nand_read(uint src_addr,uchar* dest,uint len){
- uint i = 0 , j=0;
- int col = src_addr%2048;
- /*片选*/
- nand_select();
- while(i < len){
- /*发出读命令00h*/
- nand_cmd(0x0);
- /*发出地址(5个周期)*/
- nand_addr(src_addr);
- /*发出读命令30h*/
- nand_cmd(0x30);
- /*判断状态*/
- nand_wait_ready();
- /*读数据*/
- for(;(col<2048)&&(i<len);col++){
- dest[i++] = nand_data();
- }
- if(!((j++)%20))
- _putc('#');// 打印提示
- col = 0;
- }
- /*取消片选*/
- nand_disselect();
- _puts("\n\r");
- _puts("nand_read end\n\r");
- }
- void nand_cmd(uchar cmd){/*发送nand命令*/
- volatile int i = 10;
- NFCMMD = cmd;
- while(i--);
- }
- void nand_addr(uint addr){/*发送nand地址, 5个周期, 具体参考对应的数据手册*/
- uint col = addr % 2048;
- uint page = addr / 2048;
- volatile int i;
- NFADDR = col&0xff;
- for(i = 10 ;i; i--);
- NFADDR = (col>>8)&0xff;
- for(i = 10 ;i; i--);
- NFADDR = page&0xff;
- for(i = 10 ;i; i--);
- NFADDR = (page>>8)&0xff;
- for(i = 10 ;i; i--);
- NFADDR = (page>>16)&0xff;
- for(i = 10 ;i; i--);
- }
- void nand_wait_ready(){
- while(!(NFSTAT&1));
- }
- uchar nand_data(){/*读取数据*/
- return NFDATA;
- }
- void nand_select(){/*片选*/
- NFCONT &= ~(1<<1);
- }
- void nand_disselect(){/*取消片选*/
- NFCONT |= (1<<1);
- }
接下来是清bss段
- void clear_bss()
- {
- extern int __bss_start , __bss_end;
- int *p = &__bss_start;
- _puts("clear_bss\n\r");
- while(p<&__bss_end){
- *p++ = 0;
- }
- }
好啦, 我们跳到Main函数, 也就是我们的第二阶段啦:
- void Main(){
- void (*theKernel)(int zero, int arch, uint params);
- /*1. 从nand flash 里把内核读到内存*/
- nand_read(0x60000, (uchar *)0x30008000, 0x500000);/*从0x600000拷贝0x500000 = 5M到0x30008000去*/
- /*2. 设置参数*/
- _puts("set params \n\r");
- setup_start_tag ();
- setup_memory_tags ();
- setup_commandline_tag ("noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0");
- setup_end_tag ();
- /*3. 跳转执行*/
- _puts("boot kernel \n\r");
- theKernel = (void (*)(int ,int ,uint))(0x30008000);
- theKernel (0, 1999, 0x30000100);
- _puts("error............. \n\r");
- }
- static struct tag *params;
- static void setup_start_tag ()
- {
- 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);
- }
- static void setup_memory_tags ()
- {
- 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);
- }
- static uint _strlen(char *buf){
- uint i=0;
- while(*buf++){
- i++;
- }
- return i;
- }
- static void my_strcpy(char * src ,char *dest){
- while ((*dest++ = *src++) != '\0');
- }
- static void setup_commandline_tag ( char *commandline)
- {
- int len = _strlen(commandline)+1;
- _puts("setup_commandline_tag now \n\r");
- params->hdr.tag = ATAG_CMDLINE;
- params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;/* 向4取整*/
- my_strcpy(params->u.cmdline.cmdline, commandline);
- params = tag_next (params);
- }
- static void setup_end_tag ()
- {
- params->hdr.tag = ATAG_NONE;
- params->hdr.size = 0;
- }
最后附上链接脚本:
- SECTIONS {
- . = 0x0;
- .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
- objs := start.o init.o Main.o
- boot.bin:$(objs)
- $(LD) -o boot.elf -Tboot.lds $^ # 链接
- $(OBJCOPY) -O binary -S boot.elf $@ # 转为2进制
- $(OBJDUMP) -D -m arm boot.elf > boot.dis # 反汇编
- %.o:%.c
- $(CC) -o $@ -c $< $(CPPFLAGS) $(CFLAGS)
- %.o:%.s
- $(CC) -o $@ -c $< $(CPPFLAGS) $(CFLAGS)
- clean:
- rm -r *.o *.elf *.bin *.dis
代码: