原文出处：U-boot学习笔记
作者：qq_21792169

本系列文章经作者授权在看云整理发布，未经作者允许，请勿转载！

U-boot学习笔记

U-boot学习相对来说事比较难的，因为这一块所用到的知识也比较多，但是作为一名嵌入式开发人员，移植U-boot是必要的工作。

先说下  我是结合yanghao和韦东山老师两位的博客文档自己移植U-boot来搭建整个系统，我用的u-boot-2010.09版本来移植的，对不同的开发板基本上是通用的，最多就是Not flash需要更改一些参数吧了，有可能文章写得不是很全，可以直接看补丁文件。

自己移植这一块确实挺幸苦的，今天在这里写出来，希望给大家带来帮助，同时也是给自己的一个总结，更方便查找笔记。在这里我讲写出U-boot，kernel,rootfs怎么的移植步骤，这是一个耗时间的过程，不急慢慢写，呵呵。这里面移植U-boot是最难的，你会学到很多东西，毕竟是跟硬件打交道，好了废话不多说。

虚拟机：VMWare--ubuntu9.0

开发板：SDRAM:64M    ROM:NAND FLASH 256M

U-boot下载地址：u-boot-2010.09.tar.bz2 

补丁文件下载地址：u-boot-2010.09_jz2440.patch

一， 在Uboot中添加jz2440开发板的支持

 #tar -xjvf u-boot-2010.09.tar.bz2  /*测试下自己的交叉编译器是否支持该版本的U-boot*/

 #make smdk2410_config

make  //如果编译不出错的话就可以开始移植了，我用的交叉编译器版本是一下是查看自己交叉编译器的版本

如果有错请更换交叉编译器版本。

       #arm-linux-gcc -v    //查看自己的交叉编译版本
/work/tools/gcc-3.4.5-glibc-2.3.6/lib/gcc/arm-linux/3.4.5/specs

 # cd u-boot-2010.09.tar.bz2

 #make distclean

二， u-boot移植步骤方向说明：

1.以SMDK2410为模板，建立好开发板相关的文件，并利用交叉编译器编译生成.bin文件；

2.再修改相关时钟频率使之适应开发板硬件配置，让uboot能在ram中运行调试成功（即串口能打印数据）；

3.修改norflash相关配置，使uboot能支持norfalsh的读写，附：uboot默认支持从norflash启动；

4.修改nandflash相关配置，使uboot能支持nandflash的读写；使uboot能从nand flash启动；

5.增加uboot的功能，如网络（此开发板是DM9000A）、USB等(这个功能没实现);

6.修改相应配置，使uboot能引导Linux系统启动；

7。增加yaffs2文件系统的烧写；

三、让U-B00T在内存中运行起来

（一）创建开发板文件夹

1.进入board/samsung目录，拷贝目录中的smdk2410文件夹放在当前目录下,并重命名为jz2440（这是板子配置文件夹）;

2.进入jz2440 ,把文件smdk2410.c重命名为jz2440 .c（uboot启动第二步执行的文件）;打开Makefile，修改COBJS:=jz2440 .o flash.o

（二）创建配置文件

进到include/configs目录下，找到配置文件smdk2410.h，将其拷贝并重命名为jz2440.h（uboot相关各种宏定义，关键文件）；

（三）创建编译规则

打开顶层Makefile(注意是在根目录下)，修改(红色部分)：

老版本：

1.添加编译板配置文件时用到的命令（即makejz2440 _config ;注意@之前加的是tab,不是空格）

      smdk2410_config :unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0

jz2440 _config :unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t jz2440  samsung s3c24x0   //命令前面一定留个tab行

新版本：

smdk2410        arm     arm920t         -               samsung         s3c24x0
jz2440          arm     arm920t         -               samsung         s3c24x0

四，编译U-boot

编译之前打开/include/configs/jz2440.h;增加宏定义   

 #define CONFIG_SKIP_LOWLEVEL_INIT   1 //跳过底层初始化，测试的u-boot.bin直接运行在SDRAM中

/*#define CONFIG_SKIP_RELOCATE_UBOOT    1  */ 这是针对Nor FLASH 运行的 注释掉u-boo才能初始化CPU  避免重新定位

1.make distclean //清空之前操作生成的各种文件

2.makejz2440_config //生成配置文件

3.make //编译.bin文件

若能成功编译出.bin文件，则说明交叉编译器没问题，初步移植也没问题，此时的.bin 文件只是基于smdk2410开发板的，在咱们的开发板是jz2440上不能适用的，所以在此基础上还要对相应的文件进行修改配置，例如要修改jz2440.c；jz2440.h等文件使其能支持本开发板，以上的步骤是很通用的。

到这里这个U-boot的模板就建立起来了，把u-boot.bin烧写在SDRAM中，但是不会显示任何信息，那是对的，我们还没有初始化时钟，下章我们主要在start.S中修改文件。

让串口能正常输出打印信息，添加jz2440中S3C2440片上系统的支持。

一、汇编文件修改，这部分主要集中修改修改cpu/arm920t/start.S文件。

1、打开/cpu/arm920t/start.S，删除AT91RM9200使用的LED代码，117、118行，关闭LED代码。

      - start_code:
/*
* set the cpu to SVC32 mode
*/
mrsr0,cpsr
bicr0,r0,#0x1f
orrr0,r0,#0xd3
msrcpsr,r0

 /*bl coloured_LED_init
bl red_LED_on*/     //这是LED灯初始化将其屏蔽掉,因为我们jz2440上的LED资源与SMDK2410开发板的不一致

2.找到下面的语句，定位到该位置，修改相应部位，即增加红色部分

      #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)

mov  r1, #0xffffffff
ldr    r0, =INTMSK
strr1, [r0]
# if defined(CONFIG_S3C2440)
ldrr1, =0x7fff   //这里是屏蔽中断，S3C440用到了15位，所以把值设置成0x7fff
ldrr0, =INTSUBMSK
strr1, [r0]
# endif

# if defined(CONFIG_S3C2440)
#  define MPLLCON        0x4C000004
#  define UPLLCON        0x4C000008
 ldrr0, =CLKDIVN
movr1, #5                // FCLK:HCLK:PCLK = 1:4:8
strr1, [r0]

       ldr   r0, =MPLLCON     //写MPLL使pll生效，405MHz，(127<<12)+(2<<4)+(1)
       ldr   r1, =0x7F021
       str    r1, [r0]

       ldr   r0, =UPLLCON    //USB时钟48MHz       (56<<12)+(2<<4)+(2)
       ldr   r1, =0x038022
       str    r1, [r0]

 # else

       /* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr   r0, =CLKDIVN
        mov    r1, #3
str   r1, [r0]
     # endif

#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
blcpu_init_crit
#endif

3 打开 board/samsung/ok2440v3/ok2440v3.c

 将下面一段去掉：

       #define FCLK_SPEED 1

 #ifFCLK_SPEED==0              /* Fout = 203MHz, Fin = 12MHz for Audio */
 #define M_MDIV  0xC3
 #define M_PDIV  0x4
 #define M_SDIV  0x1
 #elifFCLK_SPEED==1            /* Fout = 202.8MHz */
 #define M_MDIV  0xA1
 #define M_PDIV  0x3
 #define M_SDIV  0x1
 #endif
 #define USB_CLOCK 1

 #if USB_CLOCK==0
 #define U_M_MDIV        0xA1
 #define U_M_PDIV        0x3
 #define U_M_SDIV        0x1
 #elif USB_CLOCK==1
 #define U_M_MDIV        0x48
 #define U_M_PDIV        0x3
 #define U_M_SDIV        0x2
 #endif

用下面一段替换：

      #define M_MDIV 0x7f
#define M_PDIV 0x2
#define M_SDIV 0x1

#define U_M_MDIV 0x38
#define U_M_PDIV 0x2   //因为FL2440的晶振为12M，在芯片手册可以查到这些值，使得CPU频率为405M，USB时钟频率为48M.
#define U_M_SDIV 0x2

4 打开cpu/arm920t/s3c24x0/speed.c；修改（根据设置的分频系数FCLK:HCLK:PCLK = 1:4:8修改获取时钟频率的函数）：

      static ulong get_PLLCLK(int pllreg)
{
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
    ulong r, m, p, s;

    if (pllreg == MPLL)
r = clk_power->MPLLCON;
    else if (pllreg == UPLL)
r = clk_power->UPLLCON;
    else
hang();

    m = ((r & 0xFF000) >> 12) + 8;
    p = ((r & 0x003F0) >> 4) + 2;
    s = r & 0x3;

# if defined(CONFIG_S3C2440)
if (pllreg == MPLL)
{
return((CONFIG_SYS_CLK_FREQ * m * 2) / (p << s));
         }
# endif

    return((CONFIG_SYS_CLK_FREQ * m) / (p << s));
}

      /* return HCLK frequency */
ulong get_HCLK(void)
{
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
# if defined(CONFIG_S3C2440)
return(get_FCLK()/4);
# endif
    return((clk_power->CLKDIVN & 0x2) ? get_FCLK()/2 : get_FCLK());

}

这样，时钟就设置好了，保存，重新生成u-boot.bin，下载到开发板运行，就可以看到串口打印信息了

如果我们在include/configs/fl2440.h文件中添加

      #define  CONFIG_SKIP_LOWLEVEL_INIT     1 //用来支持uboot在内存中直接运行

然后再编译出u-boot.bin，这时的镜像文件就可以通过J-Link下载到内存0x33f80000处运行。

在board/asmsung/fl2440/fl2440.c添加如下两个宏定义用来自动补齐命令

      #define CONFIG_CMDLINE_EDITING   1

#define CONFIG_AUTO_COMPLETE     1 /*tab COMPLETE*/

如果没有Nor FLASH的同学可以跳过这一章节，直接进行下一张节。如果遇到什么问题，一般都是你没有定义那个宏之类的，这个问题可以很好的额解决。

修改norflash（nor fhash型号：JS28F320）的配置，把include/configs/fl2440.h中关于“Physical Memory Map”和“FLASH and environment organization”的配置都删掉，换成下面的配置：

      - /*-----------------------------------------------------------------------
 * Physical Memory Map
 */
#define CONFIG_NR_DRAM_BANKS1   /* we have 1 bank of DRAM */
#define PHYS_SDRAM_10x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE0x04000000 /* 64 MB */

#define PHYS_FLASH_10x00000000 /* Flash Bank #1 */
#define CONFIG_SYS_FLASH_BASEPHYS_FLASH_1
#define CONFIG_SYS_MONITOR_BASE   TEXT_BASE
#define FLASH_BASE0_PRELIM        PHYS_FLASH_1

/*-----------------------------------------------------------------------
 * FLASH and environment organization
 */
#if 0
#define CONFIG_AMD_LV4001/* uncomment this if you have a LV400 flash */
#define CONFIG_AMD_LV8001/* uncomment this if you have a LV800 flash */
#endif

#define CONFIG_SYS_MAX_FLASH_BANKS1/* max number of memory banks */
#define CONFIG_JS2_8F320 1
#define CONFIG_SYS_FLASH_PROTECTION   1
#define CONFIG_SYS_FLASH_SIZE    0x00400000    /*4 MB*/
#define CONFIG_SYS_MAX_FLASH_SECT    32         /*max number of sectors on one chip*/

/* timeout values are in ticks */
#define CONFIG_SYS_FLASH_ERASE_TOUT(2*CONFIG_SYS_HZ) /* Timeout for Flash Erase */
#define CONFIG_SYS_FLASH_WRITE_TOUT(2*CONFIG_SYS_HZ) /* Timeout for Flash Write */

#defineCONFIG_ENV_IS_IN_FLASH1
#define CONFIG_ENV_SIZE0x40000/* Total Size of Environment Sector，这是256K的环境变量储存空间 */
#define CONFIG_ENV_OFFSET  0x100000  //在以后的内核分区中环境变量分区的OFFSET值要与此一致
#endif/* __CONFIG_H */

修改board/samsung/fl2440/lowlevel_init.S文件中SDARM刷新参数

      #define REFCNT   1258

修改flash型号相关文件,用board/cmi/flash.c文件替换board/samsung/ofl2440/flash.c文件，使uboot支持Intel的JS28F320型号nor fhash.

打开board/samsung/fl2440/flash.c文件，修改：

1.把：

      #define FLASH_BLOCK_SIZE        0x00010000

改为：

      #define FLASH_BLOCK_SIZE        0x00020000

2.把声明：

      static  int   write_short   (flash_info_t *info, ulong dest, ushort data);//并删除这个函数体
改为：
static    int      write_word     (flash_info_t *info, ulong dest, ushort data);
1.
删除write_buff和write_short两个函数，用下面两个函数代替：
int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt)
{
   ulong cp, wp;
   ushort data;
   int l;
   int i, rc;
   wp = (addr & ~1);        /* get lower word aligned address */
   /*
    * handle unaligned start bytes
    */
   if ((l = addr - wp) != 0)
   {
      data = 0;
      for (i=0, cp=wp; i<l; ++i, ++cp) {
       data = (data >> 8) | (*(uchar *)cp << 8);
      }
      for (; i<2 && cnt>0; ++i) {
     data = (data >> 8) | (*src++ << 8);
     --cnt;
     ++cp;
      }
      for (; cnt==0 && i<2; ++i, ++cp) {
     data = (data >> 8) | (*(uchar *)cp << 8);
      }
      if ((rc = write_word(info, wp, data)) != 0) {
     return (rc);
      }
      wp += 2;
   }
   /*
    * handle word aligned part
    */
   while (cnt >= 2) {
      data = *((vu_short*)src);
      if ((rc = write_word(info, wp, data)) != 0) {
      return (rc);
      }
      src += 2;
      wp  += 2;
      cnt -= 2;
   }
   if (cnt == 0) {
      return ERR_OK;
   }
   /*
    * handle unaligned tail bytes
    */
   data = 0;
   for (i=0, cp=wp; i<2 && cnt>0; ++i, ++cp) {
      data = (data >> 8) | (*src++ << 8);
      --cnt;
   }
   for (; i<2; ++i, ++cp) {
      data = (data >> 8) | (*(uchar *)cp << 8);
   }
   return write_word(info, wp, data);
}

/*
 * Write 16 bit (short) to flash
 */

static int write_word (flash_info_t *info, ulong dest, ushort data)
{
   vu_short *addr = (vu_short *)dest, val;
   int rc = ERR_OK;
   int flag;

   /* Check if Flash is (sufficiently) erased , fix by kavin*/
   if ((*addr & data) != data)
      return ERR_NOT_ERASED;
   /*
    * Disable interrupts which might cause a timeout
    * here. Remember that our exception vectors are
    * at address 0 in the flash, and we don't want a
    * (ticker) exception to happen while the flash
    * chip is in programming mode.
    */
   flag = disable_interrupts();
   /* clear status register command */
   *addr = 0x50;
   /* program set-up command */
   *addr = 0x40;
   /* latch address/data */
   *addr = data;
   /* arm simple, non interrupt dependent timer */
   reset_timer_masked();
   /* wait while polling the status register */
   while(((val = *addr) & 0x80) != 0x80)
   {
      if (get_timer_masked() > CONFIG_SYS_FLASH_WRITE_TOUT) {
      rc = ERR_TIMOUT;
      /* suspend program command */
      *addr = 0xB0;
      goto outahere;
      }
   }
   if(val & 0x1A) {        /* check for error */
      printf("\nFlash write error %02x at address %08lx\n",
           (int)val, (unsigned long)dest);
      if(val & (1<<3)) {
     printf("Voltage range error.\n");
     rc = ERR_PROG_ERROR;
     goto outahere;
      }
      if(val & (1<<1)) {
     printf("Device protect error.\n");
     rc = ERR_PROTECTED;
     goto outahere;
      }
      if(val & (1<<4)) {
     printf("Programming error.\n");
     rc = ERR_PROG_ERROR;
     goto outahere;
      }
      rc = ERR_PROG_ERROR;
      goto outahere;
   }
outahere:
   /* read array command */
   *addr = 0xFF;
   if (flag)
      enable_interrupts();
   return rc;
}

到目前为止我们还不能用tftp来下载文件，因为我们还没有移植DM9000网卡。

至此，uboot就能完会支持从nor flash启动，编译生成新的u-boot.bin并通过DNW的USB线下载到SDRAM中运行，便可看到u-boot检测到了我们的flash。用flinfo命令可以看 到具体的块信息，还可以用flash的各种命令对flash进行操作，从而实现这块nor flash驱动的完全支持。烧写当然也是没问题的了，把需要烧写的文件下载到SDRAM中后，用cp.b命令就可以了。

下面大该分析一下这个驱动文件的函数调用，此flash.c文件中有如下几个函数：

      static ulong  flash_get_size         (vu_short *addr, flash_info_t *info);
static void     flash_get_offsets     (ulong base, flash_info_t *info);
static int        write_word (flash_info_t *info, ulong dest, ushort data)

unsigned long flash_init (void)
void flash_print_info  (flash_info_t *info)
int flash_erase (flash_info_t *info, int s_first, int s_last)
int flash_real_protect(flash_info_t *info, long sector, int prot)
int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt)

1.u-boot启动只会用到flash.c文件中的flash_init函数，正如上面所述；

2.以static修饰的3个函数只在flash.c文件中被调用；

3.最后面的4个函数会被/common/cmd_flash.c文件中的函数调用，以实现u-boot下的各种命令操作。
 附：若用JLINK下载u-boot.bin到nor flash 中，需要把/include/configs/fl2440.h 中定义的以下这个宏注释掉:

/* #define CONFIG_SKIP_RELOCATE_UBOOT    1  */ 注释掉u-boo才能初始化CPU

这一部分虽然移植起来简单，但是当我自己去分析的时候确实折腾了接近两天的时间，真的伤不起啊。虽然说网上对于uboot移植的DM9000A网卡的移植的资料很多，移植过程也很简单，总的来说主要就是设置一下头文件/include/configs/fl2440.h 中宏的定义和drivers/net/dm9000x.c中网卡的函数的修改，和在board/samsung/fl2440/fl2440.c中增加关于网卡的初始化。 

打开/include/configs/fl2440.h：

注释掉下面语句：

      #define CONFIG_DRIVER_CS89001/* we have a CS8900 on-board */
#define CS8900_BASE0x19000300
#define CS8900_BUS161 /* the Linux driver does accesses as shorts

增加下面语句：

      #define CONFIG_DRIVER_DM9000 1
#define CONFIG_NET_MULTI 1
#define CONFIG_DM9000_NO_SROM1
#define CONFIG_DM9000_BASE 0x20000000 //网卡片选地址
#define DM9000_IO   CONFIG_DM9000_BASE //网卡命令端口
#define DM9000_DATA(CONFIG_DM9000_BASE+4) //网卡数据端口
#define CONFIG_CMD_PING //增加ping命令
#define CONFIG_CMD_DHCP   //这个是什么 目前还不知道

加不加好像是都没有影响

      #define CONFIG_CMD_NET   //这是一个红开关  我们没有用到

定不定义都无所谓

      #define CONFIG_DM9000_USE_16BIT   1  //定义位宽

然后定义缺省的环境变量，先添加MAC地址，再修改开发板以及宿主机的IP地址：

      #define CONFIG_ETHADDR          00:0c:29:4b:94:53//我选择的俄式和虚拟机上的一样的MAC地址
#define CONFIG_NETMASK          255.255.255.0
#define CONFIG_IPADDR 210.41.141.111 //开发板IP,自定义，注意要与主机IP在同一网段
#define CONFIG_SERVERIP 210.41.141.98//主机IP
#define CONFIG_GATEWAYIP      210.41.141.1//网关，在虚拟机上我都是静态设置好了的

修改board/samsung/fl2440/fl2440.c；添加网卡初始化代码：

      #include <net.h>
#include <netdev.h>
#ifdef CONFIG_DRIVER_DM9000
int board_eth_init(bd_t *bis)
{
return dm9000_initialize(bis);
}
#endif

在drivers/net/dm9000x.c中修改：

      i = 0;
while (!(phy_read(1) & 0x20)) {/* autonegation complete bit */
udelay(1000);
i++;
if (i == 500000) {

 //将10000改为500000，这样就能解决第一次ping 不通的情况，网上说是把这段屏蔽掉，会有第一 次ping不通。
printf("could not establish link\n");
return 0;
}
}

接着在dm9000x.c中修改，屏蔽掉dm9000_halt函数中的内容，否则ping不通。 

      /*
Stop the interface.
The interface is stopped when it is brought.
*/
static void dm9000_halt(struct eth_device *netdev)
{
//DM9000_DBG("%sn", __func__);

///* RESET devie */
//phy_write(0, 0x8000); /* PHY RESET */
//DM9000_iow(DM9000_GPR, 0x01); /* Power-Down PHY */
//DM9000_iow(DM9000_IMR, 0x80); /* Disable all interrupt */
//DM9000_iow(DM9000_RCR, 0x00); /* Disable RX */
}

再修改dm9000x.c中phy_read() 函数，解决传输过程中出现“operating at unknow : 0 mode"字符串，如下：

      phy_read(int reg)
{
u16 val;

/* Fill the phyxcer register into REG_0C */
DM9000_iow(DM9000_EPAR, DM9000_PHY | reg);
DM9000_iow(DM9000_EPCR, 0xc);/* Issue phyxcer read command */
udelay(8000);  /* Wait read complete */        //默认的的是8000的就不用修改 网上说要修改1000  没有必要 这几个值可以自己测试
DM9000_iow(DM9000_EPCR, 0x0);/* Clear phyxcer read command */
val = (DM9000_ior(DM9000_EPDRH) << 8) | DM9000_ior(DM9000_EPDRL);

/* The read data keeps on REG_0D & REG_0E */
DM9000_DBG("phy_read(0x%x): 0x%x\n", reg, val);
return val;
}

接下来打开net/tftp.c (注意net是在顶层目录下)，修改TIMEOUT的值，原来是5000UL，否则会出现在超时重传的情况。

      #define TIMEOUT  70000UL /* Millisecs to timeout for lost pkt */

（注意这里的值的是不固定的，70000UL就可以正常传输，这个值也不是越大越好，

到这里，DM9000A的网卡驱动就移值完成了。

简单介绍一个用到的宏定义CONFIG_DM9000_BASE

这里需要注意的是#define CONFIG_DM9000_BASE 0x20000000 //网卡片选地址。而不是0x20003000，这个是DM9000，而我们使用的是DM9000A。因为CMD接在ADDR2上所以#define DM9000_DATA (CONFIG_DM9000_BASE+4)。因为DM9000A的地址信号和数据信号复用，CMD引脚决定传输的是地址信号还是数据信号。数据手册上说CMD为0时是地址信号所以DM9000_IO CONFIG_DM9000_BASE，CMD为1时，是数据信号，所以#define DM9000_DATA (CONFIG_DM9000_BASE+4)。DM9000A内部有一个4K Dword SRAM，因为数据线和地址线是复用的，所以如果这部分用地址线进行寻址，范围是16KB，所以在 0x20000000～0x20000000+16KB范围内都可以的。所以0x20003000是碰巧在这里的。第一次ping不同，第二次开始就可以ping通了，这个是正常现象。

到此时就可以ping通虚拟机和pc机了。

在PC机上启动tftpd32.exe  选择好服务端，IP地址，端口。就可以传输了。

tftpboot 0x30008000 210.41.141.98:zImage

在这里我折腾了很久，就是因为这个路径选择错误了，错误的选择方法如下：

      tftpboot 0x30008000 210.41.141.98:zImage  //文章并没有提示我路径错误，只是不停的打印如下信息：

lzd> tftp 0x30000000 /home/lzd/nfs/image
TFTP from server 10.107.4.145; our IP address is 10.107.4.146
Filename '/home/lzd/nfs/image'.
Load address: 0x30000000
Loading: T T T T T T T T T T
Retry count exceeded; starting again
TFTP from server 10.107.4.145; our IP address is 10.107.4.146
Filename '/home/lzd/nfs/image'.
Load address: 0x30000000
Loading: T T T T T T T T T T
Retry count exceeded; starting again

呵呵，遇到的问题越多，你学会的东西也越多。参照别人的视频教程，别人把程序和补丁都给你弄好了，然后按照他们的操作去做，你学到什么了吗？你敢换块开发板玩玩试试。我相信你自己心里也是虚的。你只是大自然的搬运工。

参考地址：http://blog.csdn.net/yanghao23/article/details/7693435

移植NAND花了我一下午的时间才把他弄明白，解决错误的途中，我也学到了更多的东西，希望大家自己要尝试亲手移植，不要老是用别人的补丁文件，自己你懂手做了才真正是你的东西。

分析了一下Uboot中Nandflash的驱动，u-boot-2009.08使用的是和Linux内核一样的MTD(内存技术设备)架构。在Uboot下对Nand的支持体现在命令行下实现对nand flash的操作，为：nand info，nand device，nand read，nand write，nand erease，nand bad。用到的主要数据结构有：struct nand_flash_dev，struct nand_chip。前者包括主要的芯片型号，存储容量，设备ID，I/O总线宽度等信息；后者是具体对nand flash进行操作时用到的信息。 

u-boot启动到第二个阶段后，在/cpu/arm920t/board.c这个文件中start_armboot函数里，有下面的代码： 

      #if defined(CONFIG_CMD_NAND)
    puts ("NAND:  ");
    nand_init();        /* go init the NAND */
#endif

 所以，我们只要定义了CONFIG_CMD_NAND这个宏，就会开始nand初始化。通过用Source Insight来做代码分析来一步步地查看函数执行过程，得出下面的nand执行流程：

1./cpu/arm920t/board.c文件中的start_armboot函数调用/drivers/mtd/nand/nand.c文件中的nand_init函数；

2.nand_init调用同文件下的nand_init_chip函数；

3.nand_init_chip函数调用/drivers/mtd/nand/s3c2410_nand.c文件下的board_nand_init函数，然后再调用/drivers/mtd/nand/nand_base.c函数中的nand_scan函数；

4.s3c2410_nand.c就是我们做移植需要实现的文件，是与具体的硬件密切相关的。

5.nand_scan函数会调用同文件下的nand_scan_ident等函数。

从这里我们得知，我们要把nand移植到2440上，就要修改s3c2410_nand.c这个文件！因为对nand flash的操作，实际上就是对nand控制器的操作，而2440的nand控制器和2410相比，有很大的不同！我们的修改工作量主要也是在这里。在这里我就在源文件上修改了。

首先在include/configs/fl2440.h中相应位置增加必要的宏定义：

      #define CONFIG_CMD_NAND
/* NAND flash settings */
#if defined(CONFIG_CMD_NAND)
#define CONFIG_SYS_NAND_BASE 0x4E000000
#define CONFIG_SYS_MAX_NAND_DEVICE     1
#define CONFIG_MTD_NAND_VERIFY_WRITE  1
#define NAND_SAMSUNG_LP_OPTIONS       1    /*注意，这个定义很重要，因为我们用的是大块nand!! */
#undef  CONFIG_ENV_IS_IN_FLASH
#define CONFIG_ENV_IS_IN_NAND     1       /* 环境变量的保存位置 */
#endif

修改/drivers/mtd/nand/Makefile,在其中添加：

COBJS-y += s3c2410_nand.o
COBJS-$(CONFIG_NAND_S3C2440) += s3c2410_nand.o 

下面的工作主要是修改drivers/mtd/nand/s3c2410_nand.c文件，首先修改27行如下

      #if  0
#define NFCONF          __REGi(NF_BASE + 0x0)
#define NFCMD           __REGb(NF_BASE + 0x4)
#define NFADDR          __REGb(NF_BASE + 0x8)
#define NFDATA          __REGb(NF_BASE + 0xc)
#define NFSTAT          __REGb(NF_BASE + 0x10)
#define NFECC0          __REGb(NF_BASE + 0x14)
#define NFECC1          __REGb(NF_BASE + 0x15)
#define NFECC2          __REGb(NF_BASE + 0x16)
#endif
#define    NFCONF         __REGi(NF_BASE + 0x0)
#define     NFCONT        __REGi(NF_BASE + 0x4)
#define    NFCMD           __REGb(NF_BASE + 0x8)
#define    NFADDR         __REGb(NF_BASE + 0xc)
#define    NFDATA          __REGb(NF_BASE + 0x10)
#define     NFMECCD0    __REGi(NF_BASE + 0x14)
#define     NFMECCD1    __REGi(NF_BASE + 0x18)
#define     NFSECCD       __REGi(NF_BASE + 0x1C)
#define    NFSTAT           __REGb(NF_BASE + 0x20)
#define    NFSTAT0         __REGi(NF_BASE + 0x24)
#define    NFSTAT1         __REGi(NF_BASE + 0x28)
#define    NFMECC0        __REGi(NF_BASE + 0x2C)
#define    NFMECC1        __REGi(NF_BASE + 0x30)
#define    NFSECC           __REGi(NF_BASE + 0x34)
#define    NFSBLK           __REGi(NF_BASE + 0x38)
#define    NFEBLK           __REGi(NF_BASE + 0x3c)

修改下面的宏：

      #define S3C2410_NFCONF_EN          (1<<15)
#define S3C2410_NFCONF_512BYTE     (1<<14)
#define S3C2410_NFCONF_4STEP       (1<<13)
#define S3C2410_NFCONF_INITECC     (1<<12)
#define S3C2410_NFCONF_nFCE        (1<<11)
#define S3C2410_NFCONF_TACLS(x)    ((x)<<8)
#define S3C2410_NFCONF_TWRPH0(x)   ((x)<<4)
#define S3C2410_NFCONF_TWRPH1(x)   ((x)<<0)
#define S3C2410_ADDR_NALE 4
#define S3C2410_ADDR_NCLE 8

uboot代码中的NAND Flash的读写驱动中存在一些错误，需要进行修改后才能完成，主要修改drivers/mtd/nand/s3c2410_nand.c文件，首先修改27行如下：

修改s3c2410_hwcontrol函数  ，这个函数用来控制发送命令还是地址。board_nand_init函数。

首先声明一个全局变量 ulong IO_ADDR_W = NF_BASE; 

      #ifdef CONFIG_S3C2410_NAND_HWECC  //这个宏没有定义，所以我们不用关心ECC之类的。

      ulong IO_ADDR_W = NF_BASE;
static void s3c2410_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
{
        struct nand_chip *chip = mtd->priv;

        DEBUGN("hwcontrol(): 0x%02x 0x%02x\n", cmd, ctrl);

         if (ctrl & NAND_CTRL_CHANGE) {
      ulong IO_ADDR_W = NF_BASE;

           IO_ADDR_W = NF_BASE;
          if (!(ctrl & NAND_CLE))
                        IO_ADDR_W |= S3C2410_ADDR_NCLE;
                if (!(ctrl & NAND_ALE))
                        IO_ADDR_W |= S3C2410_ADDR_NALE;

                //chip->IO_ADDR_W = (void *)IO_ADDR_W;

           #if defined(CONFIG_S3C2440)
        if (ctrl & NAND_NCE)
            NFCONT&= ~S3C2410_NFCONT_nFCE;  //源码中是NFCONF，S3C2410_NFCONF_nFCE

        else
           NFCONT|=S3C2410_NFCONT_nFCE;  //源码中是NFCONF,S3C2410_NFCONF_nFCE
          #endif
      }

     if (cmd != NAND_CMD_NONE)
         //     writeb(cmd, chip->IO_ADDR_W);
         writeb(cmd, (void *)IO_ADDR_W);
}

int board_nand_init(struct nand_chip *nand)

{
        u_int32_t cfg;
        u_int8_t tacls, twrph0, twrph1;
        S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
        DEBUGN("board_nand_init()\n");
        clk_power->CLKCON |= (1 << 4);

         /* initialize hardware */
        twrph0 = 0; twrph1 = 4; tacls = 2;
         cfg = 0;
        cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
        cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
        cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
       NFCONF = cfg;
     cfg = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(0<<6)|(0<<5)|(1<<4)|(0<<1)|(1<<0);
     NFCONT = cfg;

   * initialize nand_chip data structure */
        nand->IO_ADDR_R = nand->IO_ADDR_W = (void *)0x4e000010;  //NFSTAT 的地址是0x4e000010
     /* read_buf and write_buf are default */
        /* read_byte and write_byte are default */
        /* hwcontrol always must be implemented */
        nand->cmd_ctrl = s3c2410_hwcontrol;
      nand->dev_ready = s3c2410_dev_ready;

/*以下是校验码的设置，可以不用设置*/
#ifdef CONFIG_S3C2410_NAND_HWECC
        nand->ecc.hwctl = s3c2410_nand_enable_hwecc;
        nand->ecc.calculate = s3c2410_nand_calculate_ecc;
        nand->ecc.correct = s3c2410_nand_correct_data;
        nand->ecc.mode = NAND_ECC_HW3_512;
#else
        nand->ecc.mode = NAND_ECC_SOFT;
#endif
ifdef CONFIG_S3C2410_NAND_BBT
        nand->options = NAND_USE_FLASH_BBT;
#else
        nand->options = 0;
#endif
 DEBUGN("end of nand_init\n");
 return 0;
}

突然发现我自己都爱上了自己的执着精神，哈哈，回去吃完饭了。

参考地址：http://blog.csdn.net/yanghao23/article/details/7700699

很多同学使用笔记本作为自己的ARM开发和学习的平台，绝大多数笔记本都没有并口，也就是无法使用JTag调试和烧写程序到Nand Flash中，幸好我们还有JLINK，用JLINK烧写U-boot到Nor Flash中很简单，大部分NOR Flash都已经被JLink的软件SEGGER所支持，而新手在学习的时候经常会实验各种各样的命令，最悲剧的莫过于将NAND Flash中原有的bootloader给删除了，这时候开发板上电后由于没有bootloader，硬件没有被初始化，在NAND Flash中的操作系统也就无法被加载，开发板成“砖”了，这时候笔记本又无法利用JTag烧写程序进Nand Flash。起始这些可以利用JLink通过两种方法解决：

一、方法一，利用NOR Flash。

这种方法是利用JLink能够烧写程序到NOR Flash来完成的，首先利用J-FLASH ARM将u-boot.bin烧写进NOR Flash（记得烧写到NOR Flash的0x0起始地址处），然后设置开发板从NOR Flash启动，这时候系统进入U-boot命令行模式，这时候打开J-Link commander，输入命令：r 看JLink是否能识别开发板的信息（也就是判断JLink是否连接正常）。

以下是在J-Link commander里的命令，先假设u-boot.bin在你电脑的D盘根目录下。

执行命令方式如下：

      h

speed 12000               //设置TCK为12M，下载程序时会很快
loadbin d:\u-boot.bin 0x30000000     //注意：0x30000000是你想要下载u-boot.bin到开发板的内存地址，内存

//地址根据不同的开发板设定不同，因为本文中使用的是FL2440，片上系统是S3C2440，内存挂载的地址区域

//是0x30000000~0x33ffffff，我们只需要把u-boot.bin下载到这片区域即可，

然后我们在U-boot命令行模式输入NAND Flash擦除和写入命令即可：

nand erase 0 40000   //擦除从0地址开始的大小为0x40000的Nnad Flash扇区，0x40000是待写入的U-boot.bin

//的大致长度，长度必须为NAND Flash页大小的整数倍，通常会需要比u-boot.bin实际长度。

nand write 30000000 0 40000   // 把前面下载到0x30000000的u-boot.bin烧写到Nand去

二、方法二，直接通过JLink

假如你的开发板没有NOR Flash或者是你使用的NOR Flash还未被J-FLASH ARM所支持，这时上面的方法你就无法使用了，这时候你需要一个初始化内存SDRAM的程序，这个程序完成的功能也就是配置好SDRAM的寄存器，使它能正常工作，fl2440的内存初始化程序下载地址：“2440init.bin”。你还需要准备一个特殊的u-boot_SDRAM.bin，它与你要烧写到NAND Flash的u-boot.bin有区别，u-boot_SDRAM.bin编译时需要在include/configs/fl2440.h文件中添加：

      #define  CONFIG_SKIP_LOWLEVEL_INIT     1 //用来支持uboot在内存中直接运行  跳过底层初始化 。添加这个宏定义之后，U-boot就跳过了内存初始化的部分，因为此时我们的内存已经先由“2440init.bin“初始化好了，再次初始化会出现内存数据的丢失。

做好上面的准备工作之后，首先将开发板设为从NAND Flash启动，启动J-Link commander，先假设“uboot.bin”和“2440init.bin”，"u-boot_SDRAM.bin"在电脑的D盘根目录下。

h

speed 12000
loadbin d:\2440init.bin  0       //就是光盘上的初始化内存函数
setpc 0

g

h

为什么需要把"2440init.bin"复制到0x0地址是因为S3C2440有4K的SRAM，它不需要初始化就可以直接执行程序，从NAND Flash启动时，这个SRAM的地址会挂载到0x0~0x1000的地址空间，我们先把"2440init.bin"复制到SRAM中运行，执行这部分后S3C2440的SDRAM内存就初始化好了（地址空间0x30000000~0x33ffffff）。也许有人会说为什么不一开始就把u-boot_SDRAM.bin放在SRAM中运行啊？SRAM只有4K的大小，而U-boot通常在100~300K，SRAM的空间显然不够，而"2440init.bin"的大小只有不到2K，它可以在SRAM中运行。

内存初始化成功后，下载特制的u-boot_SDRAM.bin

      loadbin d:\u-boot.bin 0x30000000     //用来临时存放数据

//setpc 0x30000000     //是不能运行的，运行后会出错，相当于初始化两次SDRAM，数据将丢失
//g                                   //在这里只是把这个需要烧写的程序放在这个地址空间。
                                     //后面这个u-boot.bin(再次强调是个特殊u-boot) 你可以当作一个台阶，用着与烧写真正的u-boot.bin

loadbin d:\u-boot.bin 0x33f80000           / /0x33f80000 是把U-boot放在SDRAM内存最上面的512k的空间
setpc 0x33f80000                                   //u-boot-2010.09\board\fl2440\config.mk文件里面放在把U-boot放在内存的那个地址空间
g                                                                 //TEXT_BASE = 0x33F80000

这时你应该可以在串口看到U-boot的输出信息了，然后我们在U-boot命令行模式输入NAND Flash擦除和写入命令即可：

      nand erase 0 40000  //
nand write 30000000 0 40000 //把前面下载到0x3000000的u-boot.bin下载NAND

根据上面的步骤就可以完成u-boot到NAND Flash的烧写了，实际上是利用一个SDRAM中运行的u-boot去烧写u-boot.bin到NAND Flash中。

mv u-boot.bin u-boot_SDRAM.bin  && chmod 777 u-boot_SDRAM.bin

参考地址：http://blog.csdn.net/yanghao23/article/details/7689534

新建一个名为：cmd_menu.c的文件，放到common目录下，修改同目录下的Makefile文件，加入编译选项：
COBJS-$(CONFIG_CMD_MENU) += cmd_menu.o

编写cmd_menu.c的内容：
定义一个uboot cmd:

      U_BOOT_CMD(

    menu, 3, 0, do_menu,

    "menu - display a menu, to select the items to do something\n",

    " - display a menu, to select the items to do something"

);

cmd的名为 menu     执行的动作是do_menu函数

实现do_menu

       int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])  {
    menu_shell();
    return 0;
}

这里调用menu_shell函数

      void menu_shell(void)
{
    char cmd_buf[200];
    while (1)
    {
          main_menu_usage(); //打印菜单
          c = awaitkey(-1, NULL);
          printf("%c\n", c);
          switch (c)
          {
                case '1': //输入‘1’执行的动作
                {
                    .....
                    break;
                }
                case '2': //输入‘2’执行的动作
                {
                    break;
                }
                case '3': //输入‘3’执行的动作
                {
                    break;
                }
          }
    }
 }

menu_shell就是一个while循环，调用main_menu_usage打印菜单，然后awaitkey，再switch输入的键值，
执行相应的case

我的main_menu_usage如下：

      void main_menu_usage(void)
{
    printf("[1] 烧写bootloader到nandflash\r\n");
    printf("[2] 烧写Linux Kernel到nandflash\r\n");
    printf("[3] 烧写yaffs2文件系统到nandflash\r\n");
    printf("[4] 下载程序在SDRAM中运行\r\n");
    printf("[5] 设置uboot参数\r\n");
    printf("[6] 格式化Nandflash\r\n");
    printf("[7] 启动Linux系统\r\n");
    printf("[8] 进入Shell模式\r\n");
    printf("[9] 重启uboot\r\n");
    if (bBootFrmNORFlash == 1)
        printf("[0] 下载bootloader到NorFlash\r\n");
    printf("Enter your selection: ");
}

以烧写uboot到nandflash为例，case 1：中的内容为：

      strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase bios; nand write.jffs2 0x30000000 bios 0x100000");
run_command(cmd_buf, 0);
break;

按照这个依次写每个case

再main.c中调用menu命令：
main_loop函数中在abortboot (bootdelay) 结束后加入：

      #ifdef CONFIG_CMD_MENU
    run_command("menu", 0);
#endif

      void env_relocate (void)
      {
          /*
          * We must allocate a buffer for the environment
          */
           env_ptr = (env_t *)malloc (CFG_ENV_SIZE);

          if (gd->env_valid == 0) {
                puts ("*** Warning - bad CRC, using default environment\n\n");
              show_boot_progress (-60);
             set_default_env();
          } else {
             env_relocate_spec ();
          }
          gd->env_addr = (ulong)&(env_ptr->data);
     }

第6行  由于在配置文件中要定义环境变量区域的大小，即#define CFG_ENV_SIZE 0x10000， 这里从 heap堆里分配出这么大的空间来，并用env_ptr指向它

第7行 前面在循环体的 env_init() 中有 

       gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;

第10行 使用默认的环境变量.env_init()中已经看到default_environment[]，这是程序初始时的一份 环境变量默认设置，set_default_env()即将default_environment[]数组中的各环境变量项复制到 env_ptr所指向的env_t结构里(确切地说是复制到env_t结构的数据区里)。

第12行 环境变量存在nand中，将其从nand中读出，并填入env_ptr指向的env_t结构里 从nand读出时，配置项中有CFG_ENV_OFFSET，即环境变量在nand中存储的起始地址 CFG_ENV_SIZE 环境变量大小数据读错 或 数据校验出错，都会使用默认的环境变量配置use_default()， 第一次运行uboot时板子会打印如下信息
Warning - bad CRC or NAND, using default environment
就是因为读出的数据经过crc32校验出错(此时读出的数据不是环境变量)，进而调用use_default()，在 use_default()中会打印该信息。

第14行 又将gd->env_addr指向env_ptr->data

来看下env_t结构， 在include/environment.h中

      typedef    struct environment_s {
    uint32_t    crc;        /* CRC32 over data bytes    */
    unsigned char    data[ENV_SIZE]; /* Environment data        */
} env_t;

整个env_t结构占CFG_ENV_SIZE大小，所以data区就占CFG_ENV_SIZE － sizeof(crc) 大小，足够
使用了。

小结env_relocate 所做的事情有3件
1.从heap中分配一段空间，用于env_t结构
2.找到环境变量(或从内存中找或从nand中找)，填充env_t结构
3.将gd->env_addr指向env_ptr->data，这个也就是这里的relocate所在吧。

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

将环境变量弄完之后，紧接着就是从环境变量的相应项中获取信息，环境变量是用户与u-boot的一个交互方式， 有了它之后，用户即可通过修改环境变量来修改板子的一些信息配置。这里的ip地址和网卡地址即是其中的一 个典型例子。来看上面的程序：
第2行，获取ip地址，注意gd->bd->bi_ip_addr是ungisned long 类型，而ip地址是类似于

"192.168.1.111"的字符串。往下跟踪：

       IPaddr_t getenv_IPaddr (char *var)
         {
             return (string_to_ip(getenv(var)));
         }

getenv("ipaddr") 即在环境变量中找到ipaddr这一项对应的字符串，假设这里为"192.168.1.111"
将"192.168.1.111"传入string_to_ip。 
IPaddr_t 类型是unsigned long 的一个typedef

      IPaddr_t string_to_ip(char *s)
         {
             IPaddr_t addr;
             char *e;
             int i;

            if (s == NULL)
                return(0);

            for (addr=0, i=0; i<4; ++i) {
                ulong val = s ? simple_strtoul(s, &e, 10) : 0;
                addr <<= 8;
                addr |= (val & 0xFF);
                if (s) {
                    s = (*e) ? e+1 : e;
                }
            }

           return (htonl(addr));
       }

12~19行 192.168.1.111 分为四个段，也就是要做4次 simple_strtoul()转换成10进制的整型 第次转换后的值赋给val。addr是unsigned long型，32位的，将其分为4段，每8位存储ip地址中的一个 段，比如这里， 最后addr = (((((192 << 8) |　168) << 8) | 1) << 8 ) | 111 = 0xc0a8016f

第20行，主机字节顺序转换为网络字节顺序返回
若CPU为小端模式时，addr如下存储

31           24 23           16 15             8 7               0
+--------------+---------------+----------------+-----------------+
| 192 = 0xc0   | 168 = 0xa8    |   1 = 0x01     |  111 = 0x6f     |
+--------------+---------------+----------------+-----------------+
     3                2                  1               0

若CPU为大端模式时，addr如下存储

31           24 23           16 15             8 7               0
+--------------+---------------+----------------+-----------------+
| 111 = 0x6f   |  1 = 0x01     |  168 = 0xa8    |  192 = 0xc0     |
+--------------+---------------+----------------+-----------------+
      3                2                 1               0

当与另一台计算机通信时，通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB) 恰恰网络字节顺序跟大端模式时相同，htonl函数就是将主机字节顺序转为网络字节顺序，在最高位字节(MSB)-最前 的系统上，这些函数什么都不做。在 最低位字节(LSB)-最前的系统上它们将值转换为正确的顺序。

最后将值返回给了gd->bd->bi_ip_addr， 所以其值应该是0x6f01800a

       /* MAC Address */
             {
                 int i;
                 ulong reg;
                 char *s, *e;
                 char tmp[64];

                 i = getenv_r ("ethaddr", tmp, sizeof (tmp));
                 s = (i > 0) ? tmp : NULL;

                for (reg = 0; reg < 6; ++reg) {
                    gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
                    if (s)
                        s = (*e) ? e + 1 : e;
                }
            }

网卡地址以十六进制的形式存于gd->bd->bi_enetaddr[]数组中

uImage的制作是使用的u-boot工具mkimage，build完u-boot后也会将mkimage build出來到/tools目录下，可以直接拿來用，它的作用就是在zImage的前面加上64个字节的头，让u-boot能够识别要加载内核的类型、加载地址等。

基本格式：mkimage -n 'linux-3.4.2' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage uImage

-A 指定CPU的体系结构：（u-boot支持多种的架构，这里应该是arm）
取值        表示的体系结构
alpha       Alpha 
arm          A RM 
x86          Intel x86
ia64         IA64
mips         MIPS
mips64     MIPS 64 Bit
ppc          PowerPC
s390        IBM S390
sh            SuperH
sparc       SPARC
sparc64   SPARC 64 Bit
m68k       MC68000

-O 指定操作系统类型，可以取以下值：（u-boot支持多种的OS，这里应该是linux）
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos

-T 指定映象类型，可以取以下值：（u-boot可以加载不同的映像，这里应该是kernel）
standalone、kernel、ramdisk、multi、firmware、script、filesystem

-C 指定映象压缩方式，可以取以下值：（除了zImage本身是压缩格式外，u-boot还可以对其再压缩，这里应该是none）
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式

-a 指定映象在内存中的加载地址，可以于实际加载（如tftp加载）时的地址不相同，也可以相同，详见后面的解释。

-e 指定映像运行的入口地址，可以等于加载地址，也可以是加载地址偏移64字节（+0x40）后的地址，详见后面的解释。

-n 指定映像名称

-d 指定【源文件】和生成的【目标文件名】

所以如果使用mkimage生成内核镜像文件的话，会在内核的前头加上了64byte的信息，供建立tag之用。bootm命令会首先判断bootm xxxx 这个指定的地址xxxx是否与-a指定的加载地址是否相同。
(1)如果不同的话会从这个地址开始提取出这个64byte的头部，对其进行分析，然后把去掉头部的内核复制到-a指定的load地址中去运行之
(2)如果相同的话那就让其原封不同的放在那，但-e指定的入口地址会推后64byte，以跳过这64byte的头部。

我们来看看这三个地址的不同情况：

1> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e 
30008040 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8040 
这种情况 ，只能把 uImage download到 30008000的位置上，否则 从 30008040
是启动不了的。

原因：如果将uImage(加了头的镜像文件)下载到不同于指定加载地址的地方，则会进行上面的操作，将去掉头部的内核拷贝到指定的加载地址，此时加载地址和入口地址需要是相同的，因为已经没有镜像头了，所以此时入口地址也应该为30008000，而不应该再加上64个字节

所以在构建镜像头部中的加载地址和入口地址时千万要考虑下载的地址，否则将会启动不了。
2> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e 
30008000 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8000 
这种情况download地址随便。 还是按上面说的，因为将加载地址和入口地址设置成同样的地址，在下载到任意地址时，将去掉头部的内核镜像拷贝到指定加载地址后，可以直接从加载地址开始启动。但是要是下载地址和指定加载地址相同呢？也就是下面的：

如果 tftp 下载地址==0x30008000 ， 此时因为下载地址和指定加载地址相同，所以就不会搬动，内核直接从指定加载地址自解压啦，但是因为指定的入口地址也是0x30008000,还是在镜像头处，可以看到上面的代码，如果相同没有做任何事，只是打印了提示信息，所以还得将入口地址往后推后64个字节还是从 0x30008040 启动就肯定OK 。

所以在制作镜像头以及下载地址就有两种情况：

1，mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage zImage.img

加载地址和入口地址相同

tftp 0x31000000 zImage.img

bootm 0x31000000

下载地址可以任意放。

2,mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage zImage.img

入口地址在加载地址后面64个字节

tftp 0x30008000 zImage.img

bootm 0x30008000

下载地址一定要在指定的加载地址上。

yaffs2文件系统的移植主要涉及到u-boot对yaffs2文件系统的烧写支持、linux内核对yaffs2文件系统的支持，以及yaffs2文件系统的制作，现在我们按照从下到上的顺序来实现各部分的功能。

1、实现u-boot对yaffs/yaffs2文件系统下载的支持。

注意：这里对Nand的操作是基于MTD架构方式。

通常一个Nnad Flash存储设备由若干块组成，1个块由若干页组成。一般128MB以下容量的Nand Flash芯片，一页大小为528B，被依次分为2个256B的主数据区和16B的额外空间；128MB以上容量的Nand Flash芯片，一页大小通常为2KB。由于Nand Flash出现位反转的概率较大，一般在读写时需要使用ECC进行错误检验和恢复。

Yaffs/yaffs2文件系统的设计充分考虑到Nand Flash以页为存取单位等的特点，将文件组织成固定大小的段(Chunk)。以528B的页为例，Yaffs/yaffs2文件系统使用前512B存储 数据和16B的额外空间存放数据的ECC和文件系统的组织信息等(称为OOB数据)。通过OOB数据，不但能实现错误检测和坏块处理，同时还可以避免加载 时对整个存储介质的扫描，加快了文件系统的加载速度。以下是Yaffs/yaffs2文件系统页的结构说明：

Yaffs页结构说明

   字节                   用途
 0 - 511                  存储数据(分为两个半部)
512 - 515               系统信息
   516                      数据状态字
   517                      块状态字
518 - 519                系统信息
520 - 522                后半部256字节的ECC
523 - 524                系统信息
525 - 527                前半部256字节的ECC

好了，在了解Nand Flash组成和Yaffs/yaffs2文件系统结构后，我们再回到u-boot中。目前，在u-boot中已经有对Cramfs、Jffs2等文件系 统的读写支持，但与带有数据校验等功能的OOB区的Yaffs/Yaffs2文件系统相比，他们是将所有文件数据简单的以线性表形式组织的。所以，我们只要在此基础上通过修改u-boot的Nand Flash读写命令，增加处理00B区域数据的功能，即可以实现对Yaffs/Yaffs2文件系统的读写支持。

实现u-boot对Yaffs或者Yaffs2文件系统的读写支持步骤如下：

①、在include/configs/smdk2440.h头文件中定义一个管理对Yaffs2支持的宏和开启u-boot中对Nand Flash默认分区的宏，如下：

      #define CONFIG_MTD_NAND_YAFFS2   1 //定义一个管理对Yaffs2支持的宏

      //开启Nand Flash默认分区，注意此处的分区要和你的内核中的分区保持一致
#define MTDIDS_DEFAULT "nand0=nandflash0"
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k(bootloader)," \
                     "128k(params)," \
                     "2m(kernel)," \
                     "-(root)"

②、在common/cmd_nand.c 原来对Nand操作的命令集列表中添加Yaffs2对Nand的写命令，如下：//在U_BOOT_CMD中添加

      U_BOOT_CMD(nand, CONFIG_SYS_MAXARGS, 1, do_nand,
    "NAND sub-system",
    "info - show available NAND devices\n"
    "nand device [dev] - show or set current device\n"
    "nand read - addr off|partition size\n"
    "nand write - addr off|partition size\n"
    " read/write 'size' bytes starting at offset 'off'\n"
    " to/from memory address 'addr', skipping bad blocks.\n"

//注意：这里只添加了yaffs2的写命令，因为我们只用u-boot下载(即写)功能，所以我们没有添加yaffs2读的命令

      #if defined(CONFIG_MTD_NAND_YAFFS2)
    "nand write[.yaffs2] - addr off|partition size - write `size' byte yaffs image\n"
    " starting at offset off' from memory address addr' (.yaffs2 for 512+16 NAND)\n"
#endif

          "nand erase [clean] [off size] - erase 'size' bytes from\n"
    " offset 'off' (entire device if not specified)\n"
    "nand bad - show bad blocks\n"
    "nand dump[.oob] off - dump page\n"
    "nand scrub - really clean NAND erasing bad blocks (UNSAFE)\n"
    "nand markbad off [...] - mark bad block(s) at offset (UNSAFE)\n"
    "nand biterr off - make a bit error at offset (UNSAFE)"
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
    "\n"
    "nand lock [tight] [status]\n"
    " bring nand to lock state or display locked pages\n"
    "nand unlock [offset] [size] - unlock section"
#endif
);

接着，在该文件中对nand操作的do_nand函数中添加yaffs2对nand的操作，如下：

      if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0)
    {
        int read;

        if (argc < 4)
            goto usage;

        addr = (ulong)simple_strtoul(argv[2], NULL, 16);

        read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
        printf("\nNAND %s: ", read ? "read" : "write");
        if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
            return 1;

        s = strchr(cmd, '.');
        if (!s || !strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))
        {
            if (read)
                ret = nand_read_skip_bad(nand, off, &size, (u_char *)addr);
            else
                ret = nand_write_skip_bad(nand, off, &size, (u_char *)addr);
        }

//添加yaffs2相关操作，注意该处又关联到nand_write_skip_bad函数

      #if defined(CONFIG_MTD_NAND_YAFFS2)
        else if (s != NULL && (!strcmp(s, ".yaffs2")))
        {
            nand->rw_oob = 1;
            nand->skipfirstblk = 1;
            ret = nand_write_skip_bad(nand,off,&size,(u_char *)addr);
            nand->skipfirstblk = 0;
            nand->rw_oob = 0;
        }
#endif

        else if (!strcmp(s, ".oob"))
        {
            /* out-of-band data */
            mtd_oob_ops_t ops =
            {
                .oobbuf = (u8 *)addr,
                .ooblen = size,
                .mode = MTD_OOB_RAW
            };

            if (read)
                ret = nand->read_oob(nand, off, &ops);
            else
                ret = nand->write_oob(nand, off, &ops);
        }
        else
        {
            printf("Unknown nand command suffix '%s'.\n", s);
            return 1;
        }

        printf(" %zu bytes %s: %s\n", size, read ? "read" : "written", ret ? "ERROR" : "OK");

        return ret == 0 ? 0 : 1;
    }

③、在include/linux/mtd/mtd.h头文件的mtd_info结构体中添加上面用到rw_oob和skipfirstblk数据成员，如下：

      #if defined(CONFIG_MTD_NAND_YAFFS2)
    u_char rw_oob;
    u_char skipfirstblk;
#endif

④、在第二步关联的drivers/mtd/nand/nand_util.c 的nand_write_skip_bad函数中添加对Nand OOB的相关操作，如下：

      int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length, u_char *buffer)
{
    int rval;
    size_t left_to_write = *length;
    size_t len_incl_bad;
    u_char *p_buffer = buffer;

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support
    if(nand->rw_oob==1)
    {
        size_t oobsize = nand->oobsize;
        size_t datasize = nand->writesize;
        int datapages = 0;

        if (((*length)%(nand->oobsize+nand->writesize)) != 0)
        {
         printf ("Attempt to write error length data!\n");
         return -EINVAL;
     }

        datapages = *length/(datasize+oobsize);
        *length = datapages*datasize;
        left_to_write = *length;
    }
#endif

    /* Reject writes, which are not page aligned */
    if ((offset & (nand->writesize - 1)) != 0 ||
     (*length & (nand->writesize - 1)) != 0) {
        printf ("Attempt to write non page aligned data\n");
        return -EINVAL;
    }

    len_incl_bad = get_len_incl_bad (nand, offset, *length);

    if ((offset + len_incl_bad) >= nand->size) {
        printf ("Attempt to write outside the flash area\n");
        return -EINVAL;
    }

#if !defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support
    if (len_incl_bad == *length) {
        rval = nand_write (nand, offset, length, buffer);
        if (rval != 0)
            printf ("NAND write to offset %llx failed %d\n",
                offset, rval);

        return rval;
    }
#endif

    while (left_to_write > 0) {
        size_t block_offset = offset & (nand->erasesize - 1);
        size_t write_size;

        WATCHDOG_RESET ();

        if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) {
            printf ("Skip bad block 0x%08llx\n",
                offset & ~(nand->erasesize - 1));
            offset += nand->erasesize - block_offset;
            continue;
        }

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support
        if(nand->skipfirstblk==1)
        {
            nand->skipfirstblk=0;
            printf ("Skip the first good block %llx\n", offset & ~(nand->erasesize - 1));
            offset += nand->erasesize - block_offset;
            continue;
        }
#endif

        if (left_to_write < (nand->erasesize - block_offset))
            write_size = left_to_write;
        else
            write_size = nand->erasesize - block_offset;

        printf("\rWriting at 0x%llx -- ",offset); //add yaffs2 file system support

        rval = nand_write (nand, offset, &write_size, p_buffer);
        if (rval != 0) {
            printf ("NAND write to offset %llx failed %d\n",
                offset, rval);
            *length -= left_to_write;
            return rval;
        }

        left_to_write -= write_size;
        printf("%d%% is complete.",100-(left_to_write/(*length/100)));
        offset += write_size;

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support
        if(nand->rw_oob==1)
        {
            p_buffer += write_size+(write_size/nand->writesize*nand->oobsize);
        }
        else
        {
            p_buffer += write_size;
        }
#else
        p_buffer += write_size;
#endif

    }

    return 0;
}

⑤、在第四步nand_write_skip_bad函数中我们看到又对nand_write函数进行了访问，所以这一步是到drivers/mtd/nand/nand_base.c  的nand_write函数中添加对yaffs2的支持，如下：

      static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const uint8_t *buf)
{
    struct nand_chip *chip = mtd->priv;
    int ret;

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support

    int oldopsmode = 0;

    if(mtd->rw_oob==1)
    {
        int i = 0;
        int datapages = 0;

        size_t oobsize = mtd->oobsize;
        size_t datasize = mtd->writesize;

        uint8_t oobtemp[oobsize];
        datapages = len / (datasize);

        for(i = 0; i < (datapages); i++)
        {
            memcpy((void *)oobtemp, (void *)(buf + datasize * (i + 1)), oobsize);
            memmove((void *)(buf + datasize * (i + 1)), (void *)(buf + datasize * (i + 1) + oobsize), (datapages - (i + 1)) * (datasize) + (datapages - 1) * oobsize);
            memcpy((void *)(buf+(datapages) * (datasize + oobsize) - oobsize), (void *)(oobtemp), oobsize);
        }
    }
#endif

    /* Do not allow reads past end of device */
    if ((to + len) > mtd->size)
        return -EINVAL;
    if (!len)
        return 0;

    nand_get_device(chip, mtd, FL_WRITING);

    chip->ops.len = len;
    chip->ops.datbuf = (uint8_t *)buf;

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support

    if(mtd->rw_oob!=1)
    {
        chip->ops.oobbuf = NULL;
    }
    else
    {
        chip->ops.oobbuf = (uint8_t *)(buf + len);
        chip->ops.ooblen = mtd->oobsize;
        oldopsmode = chip->ops.mode;
        chip->ops.mode = MTD_OOB_RAW;
    }
#else
    chip->ops.oobbuf = NULL;
#endif

    ret = nand_do_write_ops(mtd, to, &chip->ops);

    *retlen = chip->ops.retlen;

    nand_release_device(mtd);

#if defined(CONFIG_MTD_NAND_YAFFS2) //add yaffs2 file system support

    chip->ops.mode = oldopsmode;
#endif

    return ret;
}