Sunday, October 16, 2011

从NAND闪存中启动U-BOOT的设计

Ref : http://www.360doc.com/content/11/0521/10/2200926_118296171.shtml
Ref : http://tw.myblog.yahoo.com/chimei-015/article?mid=521&next=519&l=f&fid=18
Ref : http://hi.baidu.com/rbdr/blog/item/63bffb5476360f4c564e00bf.html


从NAND闪存中启动U-BOOT的设计
摘要:本文介绍了S3C2410中NAND闪存的工作原理,分析了从NAND闪存启动U-BOOT的设计思路,并着重描述了NAND闪存支持U-BOOT的程序设计,移植后U-BOOT在嵌入式系统中运行良好。

关键词:U-BOOT;NAND闪存;S3C2410;嵌入式系统

引言
    随着嵌入式系统的日趋复杂,它对大容量数据存储的需求越来越紧迫。而嵌入式设备低功耗、小体积以及低成本的要求,使硬盘无法得到广泛的应用。NAND闪存 设备就是为了满足这种需求而迅速发展起来的。目前关于U-BOOT的移植解决方案主要面向的是微处理器中的NOR 闪存,如果能在微处理器上的NAND 闪存中实现U-BOOT的启动,则会给实际应用带来极大的方便。

Fig 1. U-boot Flowchart (http://tw.myblog.yahoo.com/chimei-015/article?mid=521&next=519&l=f&fid=18)


U-BOOT简介
    U-BOOT 支持ARM、 PowerPC等多种架构的处理器,也支持Linux、NetBSD和VxWorks等多种操作系统,主要用来开发嵌入式系统初始化代码 bootloader。bootloader是芯片复位后进入操作系统之前执行的一段代码,完成由硬件启动到操作系统启动的过渡,为运行操作系统提供基本 的运行环境,如初始化CPU、堆栈、初始化存储器系统等,其功能类似于PC机的BIOS。


NAND闪存工作原理
       S3C2410开发板的NAND闪存由NAND闪存控制器(集成在S3C2410 CPU中)和NAND闪存芯片(K9F1208U0A)两大部分组成。当要访问NAND闪存芯片中的数据时,必须通过NAND闪存控制器发送命令才能完 成。所以, NAND闪存相当于S3C2410的一个外设,而不位于它的内存地址区。

       NAND闪存(K9F1208U0A)的数据存储结构分层为:1设备(Device) = 4096 块(Block);1块= 32页/行(Page/row);1页= 528B = 数据块 (512B) + OOB块 (16B)
在每一页中,最后16个字节(又称OOB)在NAND闪存命令执行完毕后设置状态,剩余512个字节又分为前半部分和后半部分。可以通过NAND闪存命令00h/01h/50h分别对前半部、后半部、OOB进行定位,通过NAND闪存内置的指针指向各自的首地址。
NAND 闪存的操作特点为:擦除操作的最小单位是块;NAND闪存芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦 除;OOB部分的第6字节为坏快标志,即如果不是坏块该值为FF,否则为坏块;除OOB第6字节外,通常用OOB的前3个字节存放NAND闪存的硬件 ECC(校验寄存器)码;
  
从NAND闪存启动U-BOOT的设计思路
       如果S3C2410被配置成从NAND闪存启动,上电后,S3C2410的NAND闪存控制器会自动把NAND闪存中的前4K数据搬移到内部RAM中, 并把0x00000000设置为内部RAM的起始地址, CPU从内部RAM的0x00000000位置开始启动。因此要把最核心的启动程序放在NAND闪存的前4K中。

       由于NAND闪存控制器从NAND闪存中搬移到内部RAM的代码是有限的,所以, 在启动代码的前4K里,必须完成S3C2410的核心配置,并把启动代码的剩余部分搬到RAM中运行。在U-BOOT中, 前4K完成的主要工作就是U-BOOT启动的第一个阶段(stage1)。
根据U-BOOT的执行流程图,可知要实现从NAND闪存中启动U-BOOT,首先需要初始化NAND闪存,并从NAND闪存中把U-BOOT搬移到RAM中,最后需要让U-BOOT支持NAND闪存的命令操作。

开发环境
       本设计中目标板硬件环境如下:CPU为S3C2410,SDRAM为HY57V561620,NAND闪存为64MB的K9F1208U0A。
       主机软件环境为Redhat9.0、 u-boot-1.1.3、gcc 2.95.3。修改U-BOOT的Makefile,加入:
wch2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t wch2410 NULL s3c24x0
即将开发板起名为wch2410,接下来依次进行如下操作:
mkdir board/wch2410
cp board/smdk2410 board/wch2410
mv smdk2410.c wch2410.c
cp include/configs/smdk2410.h include/configs/wch2410.h
export PATH=/usr/local/arm/2.95.3/bin:$PATH
最后执行:
make wch2410_config
make all ARCH=arm
生成u-boot.bin,即通过了测试编译。

具体设计
支持NAND闪存的启动程序设计
       因为U-BOOT的入口程序是/cpu/arm920t/start.S,故需在该程序中添加NAND闪存的复位程序,以及实现从NAND闪存中把U-BOOT搬移到RAM中的功能程序。
       首先在/include/configs/wch2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:

#define CONFIG_S3C2410_NAND_BOOT 1      @支持从NAND 闪存中启动
然后在/cpu/arm920t/start.S中添加
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START         @安装栈的起始地址
mov fp, #0                     @初始化帧指针寄存器
bl nand_reset                  @跳到复位C函数去执行,执行NAND闪存复位
.......
/*从NAND闪存中把U-BOOT拷贝到RAM*/
ldr r0, =UBOOT_RAM_BASE        @ 设置第1个参数: UBOOT在RAM中的起始地址
mov r1, #0x0                   @ 设置第2个参数:NAND闪存的起始地址
mov r2, #0x20000               @ 设置第3个参数: U-BOOT的长度(128KB)
bl nand_read_whole             @ 调用nand_read_whole(),把NAND闪存中的数据读入到RAM中
tst r0, #0x0                   @ 如果函数的返回值为0,表示执行成功
beq ok_nand_read                @ 执行内存比较,把RAM中的前4K内容与NAND闪存中的前4K内容进行比较, 如果完全相同, 则表示搬移成功
       其中,nand_reset (),nand_read_whole()被加在/board/wch2410/wch2410.c中。

支持U-BOOT命令设计
       在U-BOOT下对nand闪存的支持主要是在命令行下实现对nand闪存的操作。对nand闪存实现的命令为:nand info(打印nand Flash信息)、nand device(显示某个nand闪存设备)、nand read(读取nand闪存)、nand write(写nand闪存)、nand erease(擦除nand闪存)、nand bad(显示坏块)等。
       用到的主要数据结构有:struct nand_flash_dev、struct nand_chip。前者包括主要的芯片型号、存储容量、设备ID、I/O总线宽度等信息;后者是具体对NAND闪存进行操作时用到的信息。

a. 设置配置选项
       修改/include/configs/wch2410.h,主要是在CONFIG_COMMANDS中打开CFG_CMD_NAND选项。定义NAND闪存控制器在SFR区中的起始寄存器地址、页面大小,定义NAND闪存命令层的底层接口函数等。

 b. 加入NAND闪存芯片型号
       在/include/linux/mtd/ nand_ids.h中对如下结构体赋值进行修改:
static struct nand_flash_dev nand_flash_ids[] = {
    ......
    {"Samsung K9F1208U0A", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0},
    .......
}
这样对于该款NAND闪存芯片的操作才能正确执行。

c. 编写NAND闪存初始化函数
在/board/wch2410/wch2410.c中加入nand_init()函数。
void nand_init(void)
{
    /* 初始化NAND闪存控制器, 以及NAND闪存芯片 */
    nand_reset();
    /* 调用nand_probe()来检测芯片类型 */
    printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}

该函数在启动时被start_armboot()调用。


最后重新编译U-BOOT并将生成的u-boot.bin烧入NAND闪存中,目标板上电后从串口输出如下信息:
U-Boot 1.1.3 (Nov 14 2006 - 11:29:50)
U-Boot code: 33F80000 -> 33F9C9E4     BSS: -> 33FA0B28
RAM Configuration:
Bank #0: 30000000 64 MB
## Unknown Flash on Bank 0: ID 0xffff, Size = 0x00000000 = 0 MB
Flash:     0 kB
NAND:     64 MB
In:       serial
Out:      serial
Err:      serial
Hit any key to stop autoboot:     0
wch2410 #


结语
       以往将U-BOOT移植到ARM9平台中的解决方案主要针对的是ARM9中的NOR闪存,因为NOR闪存的结构特点致使应用程序可以直接在其内部运行,不 用把代码读到RAM中,移植过程相对简单。从NAND闪存中启动U-BOOT的设计难点在于NAND闪存需要把U-BOOT的代码搬移到RAM中,并要让 U-BOOT支持NAND闪存的命令操作。本文介绍了实现这一设计的思路及具体程序。移植后,U-BOOT在嵌入式系统中运行良好。


APPENDIX

U-boot 流程分析
********************************************************
1.最开始系统上电后
 ENTRY(_start)程序入口点是 _start      board/mingddie/u-boot.lds

2._start:                          cpu/mips/start.S

3. la      t9, board_init_f          将函数board_init_f地址赋予t9
   j       t9                     跳转到t9寄存器中保存的地址指向的指令
                            即跳转到RAM 中执行 C 代码
                    这里会打印一些信息。
        
  3.1  board_init_f()             lib_mips/board.c
                         初始化外部内存
       relocate_code()            回到cpu/mips/start.S中继续执行


4.la t9,board_init_r                cpu/mips/start.S
  j  t9                    将函数board_init_r地址赋予t9
                                   跳转到t9寄存器中保存的地址指向的指令
                                   即跳转到RAM 中执行 C 代码
                     这里会打印一些信息

  4.1  board_init_r() 函数                  lib_mips/board.c

  4.2  main_loop()                   common/main.c  
    s=getenv ("bootcmd")          取得环境变量中的启动命令行,如bootcmd=bootm 0xbf020000
    run_command (s, 0);          //执行这个命令行 ,即bootm        
        

  4.3  do_bootm()                        common/cmd_bootm.c
      // printf ("## Booting image at %08lx ...\n", addr);   //比如

5. bootm 启动内核
   5.1 do_bootm_linux()    lib_mips/mips_linux.c




函数解析
***************************************************
1.board_init_f()

 1.1
void board_init_f(ulong bootflag)
{


    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }
        //调用init_sequence 函数队列,对板子进行一些初始化,详细见后面
    
         初始化external memory,初始化堆栈用cache作堆栈,为


    relocate_code (addr_sp, id, addr);   //回到cpu/mips/start.S 中   

    /* NOTREACHED - relocate_code() does not return */
}


 1.2
typedef int (init_fnc_t) (void);

init_fnc_t *init_sequence[] = {

    clx_board_init,        //初始化GPIO,CPU速度,PLL,SDRAM 等
    timer_init,             //时钟初始化
    env_init,        //环境变脸初始化
    incaip_set_cpuclk,    //根据环境变量设置CPU 时钟
    init_baudrate,        //初始化串口波特率
    serial_init,        /* serial communications setup */
    console_init_f,         //串口初始化,后面才能显示
    display_banner,        //在屏幕上输出一些显示信息
    checkboard,
    init_func_ram,
    NULL,
};




2.board_init_r()
(1)调用一系列的初始化函数。
(2)初始化Flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有NAND设备,则初始化NAND设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写IP、MAC地址等。
(7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作
void board_init_r (gd_t *id, ulong dest_addr)
{



    /* configure available FLASH banks */   //配置可用的flash单元
    size = flash_init();                 //初始化flash
    display_flash_config (size);        //显示flash 的大小


    /* initialize malloc() area */
    mem_malloc_init();
    malloc_bin_reloc();


    puts ("NAND:");
    nand_init();        /* go init the NAND */  //NAND初始化


    /* relocate environment function pointers etc. */
    env_relocate();                //初始化环境变量

    /* board MAC address */
    s = getenv ("ethaddr");        //以太网MAC地址
    for (i = 0; i < 6; ++i) {
        bd->bi_enetaddr[i] = s ? simple_strtoul (s, &e, 16) : 0;
        if (s)
            s = (*e) ? e + 1 : e;
    }

    /* IP Address */
    bd->bi_ip_addr = getenv_IPaddr("ipaddr");


    pci_init();         //pci初始化配置


/** leave this here (after malloc(), environment and PCI are working) **/
    /* Initialize devices */
    devices_init ();
    
    jumptable_init ();

    /* Initialize the console (after the relocation and devices init) */
    console_init_r ();              //串口初始化



    /* miscellaneous platform dependent initialisations */
    misc_init_r ();



    puts ("Net:   ");
    eth_initialize(gd->bd);


    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop ();  //循环执行,试图自动启动,接受用户从串口输入的命令,
                                                                   然后进行相应的工作,设置延时时间,确定目标板是进入下载模式还是启动加载模式
    }

    /* NOTREACHED - no way out of command loop except booting */
}

 
3.main_loop()

void main_loop (void)
{

    s = getenv ("bootdelay");  //从环境变量中取得bootdelay 内核等待延时
    bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);


        s = getenv ("bootcmd");  //从环境变量中取得bootcmd 启动命令行
                       如bootcmd=tftp;bootm 或者 bootcmd=bootm 0xbf020000
        char *s1 = getenv ("bootargs"); //从环境变量中取得bootargs 启动参数

    debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

        run_command (s, 0);    //执行启动命令




    //手动输入命令


    for (;;) {

        len = readline (CFG_PROMPT); //读取键入的命令到CFG_PROMPT 中

        rc = run_command (lastcommand, flag);  //执行这个命令

        
    }
#endif /*CFG_HUSH_PARSER*/
}

4.do_bootm()

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
这个函数看着挺长的,其实无非就是将内核解压缩,然后调用do_bootm_linux引导内核


5.do_bootm_linux()    lib_mips/mips_linux.c

打印信息Starting kernel ...

void do_bootm_linux (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[],
             ulong addr, ulong * len_ptr, int verify)
{
    
    char *commandline = getenv ("bootargs");
    

    theKernel =
        (void (*)(int, char **, char **, int *)) ntohl (hdr->ih_ep);

          //hdr为指向image header的指针,hdr->ih_ep就是我们用mkimage创建image时-e选项的参数:内核的入口地址

    linux_params_init (UNCACHED_SDRAM (gd->bd->bi_boot_params), commandline);


    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");


    theKernel (linux_argc, linux_argv, linux_env, 0);          //启动内核


}

u-boot向内核传递启动参数由一系列在include/configs/.h中的宏控制,启动参数传递的地址在board_init中初始化



参考文献
1 杜春雷 . ARM 体系结构与编程 [M]. 北京 : 清华大学出版社, 2003
2 S3C2410 User's Mannual[Z].Samsung

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Prife/archive/2008/11/09/3260582.aspx

No comments:

Post a Comment