uboot_1.1.6源码分析——第一阶段

时间:2021-02-01 04:04:16

        uboot1.1.6是一个很老的版本的uboot了,但是正因为老,所以它的文档资料也很多,更适合深入研究uboot的源码。接下来将深入分析天嵌公司tq2440开发板提供的uboot的主要源码,并且通过在熟悉源码的基础上掌握uboot的移植技术,最后弄清楚uboot的内部架构与设计规范,便于以后分析与移植其他版本的uboot到其他的开发板。分析过程采用逐个子程序的方法进行,在弄懂了每一个子程序的基础上,最后通过粗读整个程序,很快就能理解整个程序的框架。

cpu_init_crit:

     mov r0, #0  

     mcr p15, 0, r0, c7, c7, 0   /* flush v3/v4 cache */

@在《ARM体系结构与编程》的204页有关于如何设置与cache和writebuffer相关的协处理器的内容。其指令的固定格式为MCR p15,0,<Rd>,<c7>,<CRm>,<opcode_2>,其中后面两个操作码<CRm>,<opcode_2>作为参数,他们的不同组合决定指令执行不同的操作,为c7,0时,无效整个Dcache和Icache。<Rd>为要写入c7中的数据。数据也是与后面两个操作码<CRm>,<opcode_2>一起配合使用的。

     mcr p15, 0, r0, c8, c7, 0   /* flush v4 TLB */

@在《ARM体系结构与编程》的189页有关于如何设置与TLB(就是页表的cache吧)相关的协处理器的内容。其指令的固定格式为MCR p15,0,<Rd>,<c8>,<CRm>,<opcode_2>其中各个参数的意义大体同上,当后两个参数为c7,0时,使无效数据cache和指令cache,注意不是清空(无需写回脏数据),而是flush。

     mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS)清0:8,9,13,其中13决定了异常中断向量表的位置为0x0~0x1c
    bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM)清0:0,1,2,7,其中0位禁止mmu,1位禁止地址对齐检查,2位禁止数据cache7位选择小端模式
    orr    r0, r0, #0x00000002    @ set bit 2 (A) Align置1:1位使能地址对齐检查
    orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache置1:12位使能指令cache
    mcr    p15, 0, r0, c1, c0, 0

@在《ARM体系结构与编程》的177页有关于如何设置与MMU相关的协处理器的内容。主要是C1寄存器的设置。bic就是与上操作数的反码,为1的反码为0,与上后就清0了。orr直接或上去。先把c1读到r0,改下写回c1去。

    /*
     * before relocating, we have to setup RAM timing
     * because memory timing is board-dependend, you will
     * find a lowlevel_init.S in your board directory.
     */在重定位之前,我们必须设置RAM的时序,因为内存时序是独立于具体的板子的,在你的板子的目录下有一个lowlevel_init.S做这件事情
    mov    ip, lr
    bl    lowlevel_init
    mov    lr, ip
    mov    pc, lr

@ip就是r12,把lr保存到ip中,转去执行子程序,因为lr肯定在子程序中会被用到,所以要先保存在ip中。最后恢复lr,并返回调用程序。

位于board/板子名/lowlevel_init.S中有

_TEXT_BASE:
    .word    TEXT_BASE

@在链接的时候TEXT_BASE = 0x33D00000  这个是在board/板子名/config.mk文件中定义的,在前面分析uboot编译的文章中介绍了,根目录下的config.mk文件主要用来配置编译用到的一些变量等,LDFLAGS就是在这个文件中定义的。LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)。在config.mk中有CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS)  -D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE),其中的-D选项就相当于是一个在预处理期间定义到源码中去的一个宏定义,这样就让代码中的TEXT_BASE值为0x33D00000。

lowlevel_init:

    /* 位于board/板子名/lowlevel_init.S*/

    /* memory control configuration */
    /* make r0 relative the current location so that it */
    /* reads SMRDATA out of FLASH rather than memory ! */

    ldr     r0, =SMRDATA
    ldr    r1, _TEXT_BASE           @r1中的值为_TEXT_BASE单元中存储的值为TEXT_BASE,就是0x33D00000,也就是最后代码段的运行时地址
    sub    r0, r0, r1           
             @位置无关!!!不管你怎么搬代码,相对位置是不变的,位置无关的原理就是相对位置不变。在我们编译链接好了我们的程序后,这些符号都是有运行时地址的,比如,SMRDATA这个标号就是一个运行时地址,TEXT_BASE也是为0x33D00000,也是一个运行时地址。但是,可以肯定的是,刚开始这段代码在 运行时一定是在开头的4kB以内,因此,他们一定会被copy到SteppingStone里面去,那么,这时候程序怎么知道自己去哪里找到这个SMRDATA指向的符号表的内容?也就是如何找到这些配置数据呢?只有使用位置无关的方法就是计算一个相对偏移量,不管代码在哪里运行,(地址可能在变化),但是他们的相对位置(在同一个存储器里面)是不变化的。
    ldr    r1, =BWSCON    /* Bus Width Status Controller */
    add     r2, r0, #13*4

0:
    ldr     r3, [r0], #4
    str     r3, [r1], #4    
                @r3寄存器作为一个暂存的寄存器,采用事后的访问方式,先取寄存器中地址里保存的值,指令执行成功以后将偏移量加到r1中去,作为下一次的地址,实现了访问完成后地址的自动增加
    cmp     r2, r0
    bne     0b

    /* everything is fine now */
    mov    pc, lr


@SMRDATA就是存储控制器中寄存器的配置的列表,一共是13个,前9个是固定的,后4个则根据配置来,根据CONFIG_133MHZ_SDRAM(tq2440不是),选择下边那组。

    .ltorg            @在《ARM体系结构与编程》的120页,声明一个数据缓冲池的开始,一般放在代码的最后面(无条件跳转指令之后,或者子程序返回指令之后)

SMRDATA:

    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
/*    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)    */    /* HJ_del 100MHz */
/*    .word 0x32        */                            /* HJ_del 100MHz */
/*    .word 0x30        */                            /* HJ_del 100MHz */    
/*    .word 0x30        */                            /* HJ_del 100MHz */
/*    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Tsrc<<18)+REFCNT)    */        /* HJ_del 133MHz */
/*    .word 0xB2        */                            /* HJ_del 133MHz */
/*    .word 0x20        */                            /* HJ_del 133MHz */    
/*    .word 0x20        */                            /* HJ_del 133MHz */
#if CONFIG_133MHZ_SDRAM
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Tsrc<<18)+REFCNT)
    .word 0xB2
    .word 0xB0
    .word 0xB0
#else
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30
#endif


    /* Set up the stack                            */

stack_setup:

    ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot   */   @ldr指令和ldr伪指令之间的区别,ldr指令取的是存储器的内容,第二个操作数只是一个存储器地址
    sub    r0, r0, #CFG_MALLOC_LEN    /* malloc area                      */@#define CFG_MALLOC_LEN                (CFG_ENV_SIZE + 128*1024)------>256KB

@#define CFG_ENV_SIZE            0x20000    /* Total Size of Environment Sector */,0x20000就是2^17=128K
    sub    r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */

@#define CFG_GBL_DATA_SIZE            128    /* size in bytes reserved for initial data */

#ifdef CONFIG_USE_IRQ                                
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

@#define CONFIG_USE_IRQ            1         显然这里是定义了这个CONFIG_USE_IRQ

@#ifdef CONFIG_USE_IRQ
@#define CONFIG_STACKSIZE_IRQ        (4*1024)    /* IRQ stack */4KB的IRQ栈
@#define CONFIG_STACKSIZE_FIQ        (4*1024)     /* FIQ stack */4KB的FIQ栈
@#endif
#endif
    sub    sp, r0, #12        /* leave 3 words for abort-stack    */12B的abort栈
@这里可以看到在逐步地形成整个uboot的内存空间,这里我们看一张图就明白了。其中我们的TEXT_BASE=0x33D0 0000,就是61MB,那么往下就是栈区了,栈首先是256KB的CFG_MALLOC_LEN,接下来就是128B的CFG_GBL_DATA_SIZE,再接着是各4KB的CONFIG_STACKSIZE_IRQ和CONFIG_STACKSIZE_FIQ,还有12B的abort-stack ,那么最后就是真正的用户栈了,也就是sp指针指向的栈,也就是以前写C语言程序中经常提到的那个栈。

uboot_1.1.6源码分析——第一阶段

图1 uboot的内存分布

    bl clock_init

@接下来就是调用clock_init这个子程序,因为我们已经设置了用户栈了,可以直接使用伟大的C语言了,解放了,不过,我们仍然没有跳转,因此,可能是在SteppingStone上运行,我们说可能是因为它处在4KB以内而且没有搬运代码和跳转,但是也有例外就是如果我们从norflash启动的呢?根本不关nand的事!所以,总的来说,只要有可能还在SteppingStone上运行,我们就必须要用能够生成位置无关指令的C语言的代码去写程序,如不要用全局变量或者不要去访问那些静态变量或者存储在.RODATA段的数据,因为现在那些地方还是空的。

void clock_init(void) //初始化系统时钟设置为400MHZ
{

//这个函数是在board/板子名/boot_init.c中
    S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000;

/****这种用C处理寄存器的风格需要分析下,看代码:

****typedef struct {
****    S3C24X0_REG32   LOCKTIME;  其中typedef volatile u32    S3C24X0_REG32;            typedef unsigned int u32;S3C24X0_REG32定义的是 volatile unsigned int类型
****    S3C24X0_REG32   MPLLCON;按照datasheet上的寄存器的地址一字排开,然后定义一个这种结构体类型的指针变量clk_power,然后将某个具体的地址值强制转换为这种
****    S3C24X0_REG32   UPLLCON;类型的指针再赋值给clk_power这个指针变量,以后访问某个具体的寄存器直接使用这个变量去指向一个寄存器的名字(结构体的成员变量
****    S3C24X0_REG32   CLKCON;名)就可以了。
****    S3C24X0_REG32   CLKSLOW;
****    S3C24X0_REG32   CLKDIVN;
****    S3C24X0_REG32   CAMDIVN;    /* for s3c2440, by www.embedsky.net */
****} S3C24X0_CLOCK_POWER;

****/
    /* FCLK:HCLK:PCLK = ?:?:? */
#if CONFIG_133MHZ_SDRAM                       //#define CONFIG_133MHZ_SDRAM        0,因此执行else部分
    clk_power->CLKDIVN = S3C2440_CLKDIV136;            //HJ 1:3:6
#else
    clk_power->CLKDIVN = S3C2440_CLKDIV;                //HJ 1:4:8      #define S3C2440_CLKDIV        0x05    /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL */
#endif

    /* change to asynchronous bus mod */
    __asm__(    "mrc    p15, 0, r1, c1, c0, 0\n"    /* read ctrl register   */  
                    "orr    r1, r1, #0xc0000000\n"      /* Asynchronous         */  
                    "mcr    p15, 0, r1, c1, c0, 0\n"    /* write ctrl register  */  
                    :::"r1"
                    );//这就是在datasheet的243页上说的那个note吧,CPU bus mode has to be changed from the fast bus mode to the asynchronous bus mode

    /* to reduce PLL lock time, adjust the LOCKTIME register */
    clk_power->LOCKTIME = 0xFFFFFF;//时序上的锁定时间

    /* configure UPLL */
    clk_power->UPLLCON = S3C2440_UPLL_48MHZ;        //fin=12.000MHz
//    clk_power->UPLLCON = S3C2440_UPLL_48MHZ_Fin16MHz;    //fin=16.934MHz

    /* some delay between MPLL and UPLL */
    delay (4000);

/****static inline void delay (unsigned long loops)
****{
****    __asm__ volatile ("1:\n"
****            "subs %0, %1, #1\n"
****            "bne 1b":"=r" (loops):"0" (loops));
****}

*****/

   /* configure MPLL */
    clk_power->MPLLCON = S3C2440_MPLL_400MHZ;        //fin=12.000MHz         #define S3C2440_MPLL_400MHZ    ((0x5c<<12)|(0x01<<4)|(0x01))   
//    clk_power->MPLLCON = S3C2440_MPLL_405MHZ;                //HJ 405MHz
//    clk_power->MPLLCON = S3C2440_MPLL_440MHZ;                //HJ 440MHz
//    clk_power->MPLLCON = S3C2440_MPLL_480MHZ;                //HJ 480MHz
//    clk_power->MPLLCON = S3C2440_MPLL_399MHz;        //fin=16.934MHz
    /* some delay between MPLL and UPLL */
    delay (8000);
}

relocate:                /* relocate U-Boot to RAM        */

    adr    r0, _start        /* r0 <- current position of code   *///无论是从NOR Flash还是从NAND Flash启动,地址0处为U-Boot的第一条指令,因此,_start所表示的地址值0地址送r0;但如果是在调试时,uboot是下载到SDRAM中,第一条指令的地址就会是链接地址TEXT_BASE
    ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM *///TEXT_BASE就是0x33D0 0000
    cmp     r0, r1                  /* don't reloc during debug         */
    beq     clear_bss           //如果是从SDRAM启动,在调试,那么就直接跳过去,直接去做后边的事情 ,去清除bss段

    ldr    r2, _armboot_start  //r2中就是_start标号的值吧,是一个地址

/****

****.globl _armboot_start
****_armboot_start:
****    .word _start

****/

    ldr    r3, _bss_start       //r3中就是__bss_start标号的内容,是一个地址,这些是在板子目录下的u-boot.lds文件中定义的,__bss_start = .;表示它的值为bss段的起始地址

/****

****_bss_start:

****.word __bss_start

****/

    sub    r2, r3, r2        /* r2 <- size of armboot            *///r2中是要拷贝的代码的长度
#if 1
    bl  CopyCode2Ram        /* r0: source, r1: dest, r2: size */  这是一个C语言的函数,注意参数是怎么传递的,ATPCS吧,前面总结过,任何时候要拷贝都要三个东西,源地址、目标地址、拷贝的大小(用来判断何时拷贝结束)
#else
    add    r2, r0, r2        /* r2 <- source end address         *///注意这个else的代码都没有执行
copy_loop:
    ldmia    r0!, {r3-r10}        /* copy from source address [r0]    *///批量地将数据从r0指向的内存中拷贝到r3-r10中去,
    stmia    r1!, {r3-r10}        /* copy to   target address [r1]    */    //批量地将数据从r3-r10拷贝到r1所指向的内存中
    cmp    r0, r2            /* until source end addreee [r2]    */
    ble    copy_loop                                                                         //r0<=r2则没有复制完继续循环复制
#endif


int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)     //注意这里size的单位是Bytes
{//注意三个参数的意义:源地址目的地址长度;同时注意他们的类型

//board/板子名/boot_init.c文件中

    unsigned int *pdwDest;
    unsigned int *pdwSrc;
    int i;

    if (bBootFrmNORFlash())                 //判断启动flash为nor(返回值为1)还是nand的函数
    {//是norflash启动时运行的代码
        pdwDest = (unsigned int *)buf;
        pdwSrc  = (unsigned int *)start_addr;
        for (i = 0; i < size / 4; i++)                     //每一次拷贝都是4B
        {
            pdwDest[i] = pdwSrc[i];
        }
        return 0;
    }
    else
    {//是nandflash启动时运行的代码
        nand_init_ll();                               //NandFlash初始化和复位
        if (NF_ReadID() == 0x76 )           //K9F1208U0C的Device Code是0x76H,来自nandflash的datasheet的P33
            nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK));     //参数的意义发生变化:目的地址,源地址,BLOCK对齐后的长度(不足一块的补足一块)

/********   #define NAND_SECTOR_SIZE    512
/********   #define NAND_BLOCK_MASK    (NAND_SECTOR_SIZE - 1)

        else
            nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP));
        return 0;
    }
}

int bBootFrmNORFlash(void)
{

//board/板子名/boot_init.c文件中

    volatile unsigned int *pdw = (volatile unsigned int *)0;
    unsigned int dwVal;//如果是nandflash启动的话,是能写的,这种情况下还是要保存了0地址原来的那个值,并且在测试完成后写回去
    dwVal = *pdw;   
    *pdw = 0x12345678;   //pdw指向flash的0地址处,我们直接向该0地址写数据,如果能写(读出来为原值)则是nandflash启动,否则不能写为norflash启动
    if (*pdw != 0x12345678)
    {
        return 1;                               //不能写为norflash启动,返回1为norflash启动
    }
    else
    {
        *pdw = dwVal;
        return 0;                                //能写,恢复那个原来的值,并返回0为nandflash启动的
    }
}

void nand_init_ll(void)   //注意这里的nand初始化加了一个_ll的尾巴用来与后面真正的nand_init()进行区分
{//board/板子名/boot_init.c

    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;

/****typedef struct {
****    S3C24X0_REG32   NFCONF;
****    S3C24X0_REG32   NFCONT;
****    S3C24X0_REG32   NFCMD;
****    S3C24X0_REG32   NFADDR;
****    S3C24X0_REG32   NFDATA;
****    S3C24X0_REG32   NFMECCD0;
****    S3C24X0_REG32   NFMECCD1;
****    S3C24X0_REG32   NFSECCD;
****    S3C24X0_REG32   NFSTAT;
****    S3C24X0_REG32   NFESTAT0;
****    S3C24X0_REG32   NFESTAT1;
****    S3C24X0_REG32   NFMECC0;
****    S3C24X0_REG32   NFMECC1;
****    S3C24X0_REG32   NFSECC;
****    S3C24X0_REG32   NFSBLK;
****    S3C24X0_REG32   NFEBLK;
****} S3C2440_NAND;//这种寄存器的结构体定义前面已经分析过

****/

    #define TACLS   0
    #define TWRPH0  3
    #define TWRPH1  0
    s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
    s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0);               //[3]=0(默认),[1]=1指定为4个地址周期;[0]=1数据总线宽度为16位
    nand_reset();
}

static void nand_reset(void)
{//board/板子名/boot_init.c
    s3c2440_nand_reset();
}

static void s3c2440_nand_reset(void)
{//board/板子名/boot_init.c

//这个复位我们把握它的流程是4点:发出片选信号,发复位命令(0xff),等待就绪,取消片选信号
    s3c2440_nand_select_chip();
    s3c2440_write_cmd(0xff); 
    s3c2440_wait_idle();
    s3c2440_nand_deselect_chip();
}

static void s3c2440_nand_select_chip(void)        //发出片选信号
{
    int i;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;

    s3c2440nand->NFCONT &= ~(1<<1);             //Force nFCE to low (Enable chip select)
    for(i=0; i<10; i++);                                              //延时下,延时写在这里很好,后面调用这个函数做发出片选操作时完全不用考虑还要加延时等
}

static void s3c2440_nand_deselect_chip(void)              //取消片选信号
{
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;

    s3c2440nand->NFCONT |= (1<<1);                //Force nFCE to high (Disable chip select)
}

static void s3c2440_write_cmd(int cmd)        //写命令
{
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;

    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFCMD;

//特别注意这里的p的是指向unsigned char型的指针,是因为这里的NFCMD寄存器只有低8位有效
    *p = cmd;
}

static void s3c2440_wait_idle(void)                //测试RnB管脚的状态,为1时nand Flash就绪,这里是一直程序查询等待直到RnB为1
{
    int i;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFSTAT;

//NFSTAT寄存器也是只有低8位有效,*p就是读取NFSTAT寄存器的内容,其中的[0]位1: NAND Flash memory ready to operate
    while(!(*p & BUSY))             //#define BUSY            1
        for(i=0; i<10; i++);
}

int NF_ReadID(void)
{//board/板子名/boot_init.c
    char pMID;
    char pDID;
    int  nBuff;
    char   n4thcycle;
    int i;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;

    b128MB = 1;
    n4thcycle = nBuff = 0;

    nand_init_ll();                          //复位下nand flash
    nand_select_chip();                //使能片选信号
    write_cmd(0x90);    // read id command
    *p=0x00 & 0xff;                     //这么写还是0x00,但是确保是一个字节的0x00
    for ( i = 0; i < 100; i++ );

    pMID = read_data();          //Marker Code 0xECH
    pDID =  read_data();         //Device Code 0x76H
    nBuff =  read_data();         //0x5AH
    n4thcycle = read_data();   //0x3FH

    nand_deselect_chip();          //禁止片选信号
    
    if (pDID >= 0xA0)
    {
        b128MB = 0;
    }

    return (pDID);
}

static unsigned char read_data(void)
{
    return s3c2440_read_data();
}

static unsigned char s3c2440_read_data(void)
{
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA;
    return *p;     //熟悉这种模式,定义一个指针指向这个寄存器的地址,然后用*去引用这个指针,就可以得到这个寄存器的内容,定义的是指向unsigned char的指针则获得低8位的数据。所以是分两步走,第一步p指针指向寄存器的地址,第二步用*号才是真正去读。
}

int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
    int i, j;
    char dat;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;

    
    if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))        //源地址和长度的block对齐检查
    {
        return -1;   
    }

    nand_select_chip();                         //使能片选信号

    for(i=start_addr; i < (start_addr + size);)
    {
/* Check Bad Block */
if(1){

        write_cmd(0x50);                         //read2命令

uboot_1.1.6源码分析——第一阶段


        *p = 5;                            //指定C区要读取的起始地址(16B)
        for(j=0; j<10; j++);
        *p = (i >> 9) & 0xff;
        for(j=0; j<10; j++);
        *p = (i >> 17) & 0xff;
        for(j=0; j<10; j++);
        *p = (i >> 25) & 0xff;
        for(j=0; j<10; j++);
        wait_idle();

        dat = read_data();        //根据时序读出这个字节

        write_cmd(0);               // nandflash的datasheet说The Read1 command(00h/01h) is needed to move the pointer back to the main area             

        nand_deselect_chip();
        if(dat != 0xff)          //判断是否是坏块,至于为什么是这么判断的,暂时没明白,反正与OOB相关,看datasheet
            i += 16384;        // 1 Block = 512*32= 16384   //难道不考虑连续的坏块的问题吗?
/* Read Page */
        nand_select_chip();
}
        write_cmd(0);

        /* Write Address */
        write_addr(i);            //写地址
        wait_idle();                //写完地址,要用wait_idle()等,注意这些需要等的操作

        for(j=0; j < NAND_SECTOR_SIZE; j++, i++)               //一次循环拷贝一个字节,整个循环拷完一块
        {
            *buf = read_data();
            buf++;
        }
    }

    nand_deselect_chip();                //每次拷完一整块都是禁止片选信号

    return 0;

}

static void write_addr(unsigned int addr)
{
    s3c2440_write_addr(addr);
}

 static void s3c2440_write_addr(unsigned int addr)
{
    int i;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;
    
    *p = addr & 0xff;
    for(i=0; i<10; i++);
    *p = (addr >> 9) & 0xff;
    for(i=0; i<10; i++);
    *p = (addr >> 17) & 0xff;
    for(i=0; i<10; i++);
    *p = (addr >> 25) & 0xff;
    for(i=0; i<10; i++);
}


/*
  * Large Page
  */
int nand_read_ll_lp(unsigned char *buf, unsigned long start_addr, int size)
{
    int i, j;
    char dat;
    S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;

    if ((start_addr & NAND_BLOCK_MASK_LP) || (size & NAND_BLOCK_MASK_LP))
    {
        return -1;  
    }

    nand_select_chip();

    for(i=start_addr; i < (start_addr + size);)
    {
/* Check Bad Block */
if(1){
        int col, page;

        col = i & NAND_BLOCK_MASK_LP;
        page = i / NAND_SECTOR_SIZE_LP;
        write_cmd(0x00);

        *p = 5;
        for(j=0; j<10; j++);
        *p = 8;
        for(j=0; j<10; j++);
        *p = page & 0xff;        /* Row Address A12~A19 */
        for(j=0; j<10; j++);
        *p = (page >> 8) & 0xff;        /* Row Address A20~A27 */
        for(j=0; j<10; j++);
if (b128MB == 0)
        *p = (page >> 16) & 0x03;        /* Row Address A28~A29 */
        for(j=0; j<10; j++);

        write_cmd(0x30);
        wait_idle();

        dat = read_data();
       
        nand_deselect_chip();
        if(dat != 0xff)
            i += 131072;        // 1 Block = 2048*64= 131072
/* Read Page */
  
        nand_select_chip();
}
        write_cmd(0);

        /* Write Address */
        write_addr_lp(i);
        write_cmd(0x30);
        wait_idle();

        for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++)
        {
            *buf = read_data();
            buf++;
        }
    }

    nand_deselect_chip();

    return 0;
}

clear_bss:                            /*清除bss段*/

    ldr    r0, _bss_start        /* find start of bss segment        */
    ldr    r1, _bss_end        /* stop here                        */
    mov     r2, #0x00000000        /* clear                            */


clbss_l:

    str    r2, [r0]        /* clear loop...                    */
    add    r0, r0, #4
    cmp    r0, r1
    ble    clbss_l
         //就一个循环通过b的le条件执行(r0<=r1时执行)不断地把0x00000000送到__bss_start(注意是两根下划线__)

ldr    pc, _start_armboot             //ldr不是一个地址无关指令,但是这里已经没问题了,因为代码已经搬到了sdram对应的位置了
_start_armboot:    .word start_armboot