(嵌入式开发)自己写bootloader之编写第一阶段
最简单的bootloader的编写步骤:
1. 初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH
2. 如果bootloader比较大,要把它重定位到SDRAM
3. 把内核从NAND FLASH读到SDRAM
4. 设置"要传给内核的参数"
5. 跳转执行内核
改进:
1. 提高CPU频率, 200MHZ ==> 400MHZ
2. 启动ICACHE
重定位
分为nor启动和nand启动
int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0;
int val;
val = *p;
*p = 0x12345678;
if (*p == 0x12345678)
{
/* 写成功, 是nand启动 */
//nand启动时,0地址对应内存,内存是可以写的
*p = val;//回复原来的值
return 0;
}
else
{
/* NOR不能像内存一样写 */
return 1;
}
}
{
volatile int *p = (volatile int *)0;
int val;
val = *p;
*p = 0x12345678;
if (*p == 0x12345678)
{
/* 写成功, 是nand启动 */
//nand启动时,0地址对应内存,内存是可以写的
*p = val;//回复原来的值
return 0;
}
else
{
/* NOR不能像内存一样写 */
return 1;
}
}
TACLS、TWRPH0 、TWRPH1的设置
nand_read
读页数据读到页寄存器
nand结构
bootloader的最终目的是启动内核,而在启动内核前要进行一系列的初始化: 关闭看门狗、改变系统时钟、初始化存储控制器、重定位代码(将更多的代码复制到内存中去)等, 然后将内核从nand flash读到SDRAM中,为内核传递启动参数,跳到相应的地址启动内核。
<pre name="code" class="objc" style="widows: 1;">#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02)) #define MEM_CTL_BASE 0x48000000
<pre name="code" class="objc" style="widows: 1;">.text //指定了后续编译出来的内容放在代码段【可执行】; .global _start //告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】; _start: /*_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的, 必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中, 供其它程序【如加载器】寻找到。*/
1. 关闭看门狗 向WTCON寄存器WTCON中写入零 汇编代码: ldr r0, =0x53000000 mov r1, #0 str r1, [r0] C代码:(调用C代码之前必须先设置栈,即sp指针,指令mov sp, #4096) #define WTCON (*(volatile unsigned long *)0x53000000) void disable_watch_dog(void) { WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可 } 2. 设置系统时钟 汇编代码: #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) 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] C代码: void clock_init(void) { // 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" /* 写入控制寄存器 */ );
/******************************************************************* * 时钟初始化函数 * 对于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 * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4 * 由于开发板的输入时钟为12MHz,而且设置MDIV PDIV SDIV分别为 * S3C2410 : MDIV=0x5C PDIV=0x04 SDIV=0x00 * S3C2440 :MDIV=0x12 PDIV=0x01 SDIV=0x02 * 则有:FCLK=200MHz HCLK=100MHz PCLK=50MHz *******************************************************************/ MPLLCON = S3C2440_MPLL_200MHZ; /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */ } //3. 初始化SDRAM 汇编代码: ldr r0, =MEM_CTL_BASE adr r1, sdram_config /* sdram_config的当前地址 */
add r3, r0, #(13*4) 1: ldr r2, [r1], #4//将r1地址中的内容存到r2中,同时r1=r1+4 str r2, [r0], #4//将r2中的值存到r0所指定的地址中, 同时r0=r0+4 cmp r0, r3 // 比较r0和r1的值 bne 1b // bne 表示如果不相同跳转的标号为1的地方,后面跟一个b表示跳转到前面的1标号,如果跳转到后面去将b改为f即可 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 C代码: void memsetup(void) { 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 } /* * 初始化SDRAM后,必须重新设置栈,且将sp指针内存的指向最高,因为栈是重高地址向低地址向下增长的, * 即使用命令ldr sp, =0x34000000 (将0x34000000赋值给sp指针,ldr是一条伪指令,当0x34000000数字很大的时候不能转换为一个立即数的时候,会通过几条汇编指令来完成) */ 4. 初始化nand控制器 bl nand_init // 汇编调用C函数 /* 初始化NAND Flash */ void nand_init(void) { // 这三个值结合S3C2440手册和nand flash手册设置时序 #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); } //5. 重定位代码 // 汇编中调用C函数时,r1传递函数的第一个参数,r2传递函数的第二个参数,r3传递函数的第三个参数 mov r0, #0//从0地址开始复制 ldr r1, =_start // 来自汇编代码的第一行 // .text // .global _start // _start: ldr r2, =__bss_start // __bss_start 来自链接脚本 sub r2, r2, r1 bl copy_code_to_sdram 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 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(); } 链接脚本为: SECTIONS { . = 0x33f80000; // 起始链接地址 .text : { *(.text) } // 代码段 . = ALIGN(4); // 四字节对齐 .rodata : {*(.rodata*)} // 只读数据段 . = ALIGN(4); .data : { *(.data) } // 数据段 . = ALIGN(4); __bss_start = .; //bss段开始地址 .bss ALIGN(4) : { *(.bss) *(COMMON) } __bss_end = .; //bss段结束地址 }