从零写bootloader--第一阶段启动的实现

来源:互联网 发布:模拟经营单机 知乎 编辑:程序博客网 时间:2024/04/30 14:59

韦东山视频学习笔记。平台JZ2440。CPU为S3C2440A,NandFlash型号为K9F2G08U0M。本文如果有什么错误的地方,欢迎各位批评指正,不甚感激。

注:第一阶段部分函数未完成,还不能启动内核

一、剖析实现从零写bootloader我们所要做的工作:

        要想从零写出自己的bootloader,我们得先知道bootloader的目的是干嘛。就好像我们做事,得先明确目的,才有具体的行动。

        bootloader的目的:启动内核

        要启动内核,我们得先知道内核在哪?内核很大,通常存储于NandFlash上(较NorFlash:容量大、块小、 擦写快、便宜)。这里我们可以初步           确定我们的工作:

               工作1:从NandFlash上把内核读入内存

                      1、能读NandFlash

                      2、初始化内存

               工作2:启动内核

                      3、设置参数(如:告诉内核我的内存有多大;去哪挂接根文件系统……)

                      4、跳转执行

               工作3:其他

               5、关看门狗;

            6、提高运行速度,初始化时钟(系统上电12M);

           7、清零bss段(存放未初始化的全局变量和初始化为零的全局变量)

                      8、……


        到这里,我们所要做的工作就是完成上述基本工作,那bootloader第一阶段就基本完成了。下面有三个代码实现文件start.s、init.c、boot.ldsstart.s是执行的第一个文件;init.c是一些函数C语言的实现和一些初始化函数,start.s中大部分跳转函数在这里边实现;boot.lds是链接脚本。


二、实现代码


1、start.s


#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, =0x53000000mov r1, #0str r1, [r0]/* 2. 设置时钟 */ldr r0, =0x4c000014//mov r1, #0x03;  // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1mov r1, #0x05;  // FCLK:HCLK:PCLK=1:4:8str r1, [r0]//当FCLK != HCLK时,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */mrcp15, 0, r1, c1, c0, 0/* 读出控制寄存器 */ orrr1, r1, #0xc0000000/* 设置为“asynchronous bus mode” */mcrp15, 0, r1, c1, c0, 0/* 写入控制寄存器 *//* MPLLCON = S3C2440_MPLL_200MHZ */ldr r0, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr r1, [r0]/* 启动ICACHE */mrc p15, 0, r0, c1, c0, 0@ read control regorr r0, r0, #(1<<12)mcrp15, 0, r0, c1, c0, 0   @ write it back/* 3. 初始化SDRAM */ldr r0, =MEM_CTL_BASEadr r1, sdram_config     /* 得到sdram_config标号的当前地址 */add r3, r0, #(13*4)        //r3 = r0 + 521:ldr r2, [r1], #4         //从r1所指的地方取一个值,存到r2,然后r1加4str r2, [r0], #4         //把r2的值存到r0的地址上,然后r0加4cmp r0, r3bne 1b                   //b表示back:表示前面的1标号;可以有多个1标号(1f:forward)/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */ldr sp, =0x34000000     //我们内存是64M,基地址是0x30000000,到0x34000000是64M大小,指向最高内存(栈是向下增长的)/* 不管是NandFlash启动还是NorFlash启动,内核都存在NandFlash上,所以都得先初始化NandFlash */bl nand_init/*汇编向C函数传递参数(参数1:r0、参数2:r1、参数3:r2)*/mov r0, #0             //源地址ldr r1, =_start        //目的地址(即需要拷贝到内存中的链接地址)ldr r2, =__bss_startsub r2, r2, r1         //长度r2 = r2 - r1bl copy_code_to_sdram  //接受r0、r1、r2三个参数给函数/*拷贝完后,将未初始化的全局和初始化为零的全局内存空间清零*/bl clear_bss/* 5. 执行main */ldr lr, =haltldr pc, =mainhalt:b haltsdram_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


/* 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);/** 判断方法:像零地址写数据* NorFlash启动时从NorFlash零地址开始执行,向零地址写数据就是往NorFlash上写数据,会失败(Norflash可以像内存一样读,但不能像内存一样写)* NandFlash启动时,零地址为片内内存,向内存写数据,读出后会写成功。*/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);}}/* C语言如何引用__bss_start等标号的典型实例******************************************** * 汇编中引用: * ldr r1, =_start        //目的地址(即需要拷贝到内存中的链接地址) *ldr r2, =__bss_start *sub r2, r2, r1         //长度r2 = r2 - r1 */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);elseputc('A' + j - 0xa);}}


3、boot.lds


SECTIONS {    . = 0x33f80000; //跟最大地址刚好相差512K,我们的程序肯定不会超过512K,所以选该地址    .text : { *(.text) }        . = ALIGN(4);    .rodata : {*(.rodata*)}         . = ALIGN(4);    .data : { *(.data) }/*bss段存放没有初始化的全局变量或则初始化为0的全局变量*///我们编译出来的二进制文件不会含有这些初始化为0的变量//因为bss不是文件里的东西,那可知代码段大小(文件大小) = (__bss_start - _start(0x33f80000))    . = ALIGN(4);    __bss_start = .;    .bss : { *(.bss)  *(COMMON) }    __bss_end = .;}



<pre code_snippet_id="297595" snippet_file_name="blog_20140417_1_9879096" name="code" class="csharp"><pre code_snippet_id="297595" snippet_file_name="blog_20140417_1_9879096">
                                             
0 0
原创粉丝点击