Chinaunix首页 | 论坛 | 博客
  • 博客访问: 61894
  • 博文数量: 4
  • 博客积分: 202
  • 博客等级: 入伍新兵
  • 技术积分: 78
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-14 00:11
文章分类
文章存档

2012年(1)

2011年(3)

我的朋友

分类: LINUX

2012-06-01 09:41:55

    大家都知道,nandflash只会保证第一个不是坏块,后面任何一个都有可能是坏块,如果你的使用的nandflash的blocksize是128K,而你的uboot编译出来大于128K,而且很不幸的是,nand的第二块是一个坏块,我使用的处理器是三星的6410,而官方提供的bl2加载是没有坏块校验的,所以后来高版本的uboot专门做了一个nand_spl,它单独编译出一个bin文件,然后和你的uboot拼接起来,该部分是有坏块校验的,但是三星官方的却没有这一功能,我自己也懒得加进去,因为无非就是一个坏块校验而已,自己实现一个简单的校验函数就可以啦,说干就干,三星官方uboot的bl2加载在nand_cp.c文件中实现,start.s里面调用copy_uboot_to_ram函数,再由该函数对bl2进行加载,下面是我修改的代码,这里就不列出官方的原始代码,因为网上可以随便下载,自己下了对比一下就知道区别啦!
 
//下面的几个宏其实就是全局变量,由于bl2的加载必须在前4K里面完成,至于为什么,请百度,所以这里我没有定义
//成普通的全局变量,因为这样会越过4K而造成程序无法运行,官方的是使用参数传递的办法,可是我在nandll_read_block
//函数里面加了一个参数就造成了程序运行失败,具体没有找到,应该不是堆栈溢出,但是又解释不了,所以干脆定义全局的
//我这里是定义了几个宏,放到指定的内存,注意,该地址一定不要和uboot拷贝的地址重叠,否则会失败,下面对该宏读写就
//可以啦。
#define block_size      (*(volatile unsigned long *)(MEMORY_BASE_ADDRESS + 0x00))
#define page_size      (*(volatile unsigned long *)(MEMORY_BASE_ADDRESS + 0x04))
#define page_shift     (*(volatile unsigned long *)(MEMORY_BASE_ADDRESS + 0x08))
#define large_block      (*(volatile unsigned long *)(MEMORY_BASE_ADDRESS + 0x0c))
/*
 * address format
 *              17 16         9 8            0
 * --------------------------------------------
 * | block(12bit) | page(5bit) | offset(9bit) |
 * --------------------------------------------
 */
 //哈哈,这个函数才是重头戏,我重点说一下啊。
static int nand_is_bad_block(int block)
{
 int i,ret=0;
 char bad;
 ulong addr=0;
 
 addr=block * (block_size>>page_shift);
 //参数block是要判断哪一块,addr是根据block计算出来是哪一页,如果是一个正常
 //的块,那么它的oob区第一个字节一定是0xff,如果是一个坏块,那么oob区全部都是0x00
 //oob区在每一个块的结尾,不算是块的大小,比如一个nand的block_size是128K,oob是64个字节
 //那么oob区会在这一个页的后面
        NAND_ENABLE_CE();
        NFCMD_REG = NAND_CMD_READ0;
        /* Write Address */
        NFADDR_REG = 0;
 if (large_block)
         NFADDR_REG = 0;
 NFADDR_REG = (addr) & 0xff;
 NFADDR_REG = (addr >> 8) & 0xff;
 NFADDR_REG = (addr >> 16) & 0xff;
 if (large_block)
  NFCMD_REG = NAND_CMD_READSTART;
        NF_TRANSRnB();
 /* skip a page*/
 for(i=0; i < page_size; i++) {
                bad = NFDATA8_REG;
       }
      
 /*
  * Read one byte
  */
 
 if(NFDATA8_REG != 0xff)
 {
  ret=1;
 }
 //先读一个完整的页数据,后面再接着读一个字节就是oob区的第一个字节啦,然后就可以判断啦,哈哈!
        NAND_DISABLE_CE();
 return ret;
}
/*
 * address format
 *              17 16         9 8            0
 * --------------------------------------------
 * | block(12bit) | page(5bit) | offset(9bit) |
 * --------------------------------------------
 */
static int nandll_read_page (uchar *buf, ulong addr)
{
        int i;
 
        NAND_ENABLE_CE();
        NFCMD_REG = NAND_CMD_READ0;
        /* Write Address */
        NFADDR_REG = 0;
 if (large_block)
         NFADDR_REG = 0;
 NFADDR_REG = (addr) & 0xff;
 NFADDR_REG = (addr >> 8) & 0xff;
 NFADDR_REG = (addr >> 16) & 0xff;
 if (large_block)
  NFCMD_REG = NAND_CMD_READSTART;
        NF_TRANSRnB();
 /* for compatibility(2460). u32 cannot be used. by scsuh */
 for(i=0; i < page_size; i++) {
                *buf++ = NFDATA8_REG;
        }
        NAND_DISABLE_CE();
        return 0;
}
/*
 * Read data from NAND.
 */
static int nandll_read_blocks (ulong dst_addr, ulong size)
{
        uchar *buf = (uchar *)dst_addr;
        int i=0,j=0,block_num=0,page_num;
 //下面我是根据uboot的size和nand的bolck_size决定需要加载几个块,
 //有些人一定觉得我的计算办法很弱智,为什么不用/和%运算呢?其实
 //我也想知道为什么,前面我加参数不能启动,这里如果用/和%运算一样
 //会崩溃,首先我保证没超过4k,但是我定义了sp指针,我也没有限制堆栈
 //的大小啊,为什么稍微复杂一点就不行呢?哪位大侠如果知道一定要告诉我
 //谢谢!
 if(block_size==0x40000)
 {
  block_num=size >> 18;
 }
 else if(block_size==0x20000)
 {
  block_num=size >> 17;
 }
 else
 {
  block_num=size >> 14;
 }
 if((block_num*block_size) < size)
 {
  block_num++;
 }
        /* Read pages */
        //下面就是读每一块的数据,我是一块一块的读,每一块都会加坏块判断,如果是坏块我就会跳过,然后需要读的块数加一
 for(i=0;i {
  if(!nand_is_bad_block(i))
  {
   for (j = i*(block_size>>page_shift); j < (i+1)*(block_size>>page_shift); j++, buf+=(1<   {
                  nandll_read_page(buf, j);
          }
  }
  else
  {
   block_num++;
  } 
 }
        return 0;
}
int copy_uboot_to_ram (void)
{
 int i;
 vu_char extid;
 
        NAND_ENABLE_CE();
        NFCMD_REG = NAND_CMD_READID;
        NFADDR_REG =  0x00;
 /* wait for a while */
        for (i=0; i<200; i++);
 extid = NFDATA8_REG;//maker id
 extid = NFDATA8_REG;//device id
 //nand有四个ID号,device id比较小的是使用查表的办法获取nand参数
 //但是现在基本已经没有那种nand了,现在都是使用第四个id去计算获得
 //参数,下面就是计算的办法,分别计算出page_size和block_size。
 if (extid > 0x80)
 {
  /* The 3rd id byte holds MLC / multichip data */
  extid = NFDATA8_REG;
  /* The 4th id byte is the important one */
  extid = NFDATA8_REG;
  /* Calc pagesize */
  page_size = 1024 << (extid & 0x3);
  extid >>= 2;
  /* Calc oobsize */
  //oobsize = (8 << (extid & 0x01)) * (page_size >> 9);
  extid >>= 2;
  /* Calc blocksize. Blocksize is multiples of 64KiB */
  block_size = (64 * 1024) << (extid & 0x03);
  
  page_shift = 11;
  large_block = 1;
 }
 else
 {
  block_size=0x4000;
  page_size=512;
  page_shift=9;
  large_block=0;
 }
 return nandll_read_blocks(CFG_PHY_UBOOT_BASE, 0x40000);//这里是将nand的前256k加载到内存
}
到此,坏块的问题解决了,但是你运行一下然后sav环境变量,你会悲催的发现,不行,为什么呢?
看这个宏CFG_ENV_OFFSET,我的是0x3c000,正好在坏块里面,官方的默认都是你的uboot的分区是一定没有坏块的,
所以bl2加载和环境变量的读写根本就没有校验机制,本来想加校验,但是假如要从0x3c000读写环境变量,当判断到
这一个是坏块,那么它是直接跑到下一块,0x40000开始写,这下完蛋了,因为我们在烧写uboot的时候,比如是烧写
0x40000正好两个块,当看到第二个块是坏的时候,也是跑到了,0x40000往后写,正好地址重叠,这就造成你sav环境
变量后将你的uboot的代码给覆盖了,下次就启动不了啦,所以我们希望的是这样的,比如我的初始偏移是0x3c000,当
发现第二个块是坏块后,我们向后偏移一个块,变成0x5c000,这样再次保存就不是从0x40000开始啦,这次修改其实挺
简单,只需要加一个环境变量偏移地址重定位即可,然后只需要将saveenv函数和env_relocate_spe函数中的CFG_ENV_OFFSET
替换为新的便宜即可,下面是我的实现代码
int cfg_env_off=CFG_ENV_OFFSET;//这个是我定义的新的便宜地址,需要将将替换掉
//saveenv函数和env_relocate_spe函数中的CFG_ENV_OFFSET

//该函数我略作修改,以前都是前面有多少全部读出来,比如偏移是0x3c000
//他会将全面的0x3c000全部读出来,假如块大小是0x40000,那么这样做没问题
//但是假如是0x20000,根本就不需要读那么多,只需要读0x1c000就可以啦,所以
//我做了一些修改,自动根据块的的大小去决定读多少和写多少。
int saveenv(void)
{
 ulong total;
 int ret = 0,i;
 
 total = cfg_env_off % nand_info[0].erasesize;
 //官方的使用malloc去分配内存,但是测试发现会出问题,也没太在意咋回事
 //我这个分配内存的方式在标注c语言里面肯定是不行的,因为不能用变量去分配数组
 //但是gnu的c语言是可以的,我不提倡这么用。
 u_char tmp_env1[total];
 
 nand_read(&nand_info[0], cfg_env_off-total, &total, tmp_env1);
 puts ("Erasing Nand...");
 if (nand_erase(&nand_info[0], cfg_env_off-total, nand_info[0].erasesize))
  return 1;
 
 puts ("Writing to Nand... ");
 
 ret = nand_write(&nand_info[0], cfg_env_off-total, &total, tmp_env1);
 
 total = CFG_ENV_SIZE;
 ret = nand_write(&nand_info[0], cfg_env_off, &total, (u_char*)env_ptr);
 if (ret || total != CFG_ENV_SIZE)
  return 1;
 
 puts ("done\n");
 return ret;
}
//这个函数就是重定位环境变量偏移地址啦,0x80000是我的uboot分区的大小
//0x20000是我的块大小,meminfo->block_isbad(meminfo, offs)这个函数
//记录了nand的坏块信息,所以很简单就知道哪一个是坏块啦,遇到坏块,就
//加0x20000
void env_off_relocation(void)
{
 nand_info_t *meminfo=&nand_info[0];
 int offs;
 int ret;
 for(offs=0x20000;offs<=0x80000;offs+=0x20000)
 {
  ret = meminfo->block_isbad(meminfo, offs);
  if (ret < 0)
  {
   printf("Bad block check failed\n");
   return -1;
  }
  if (ret == 1)
  {
   cfg_env_off+=0x20000;
  }
  else
  {
   break;
  }
 }
 printf("new cfg_env_off=%x\n",cfg_env_off);
}
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
 ulong total;
 int ret;
 
 env_off_relocation();//该处调用了环境变量偏移地址重定位的功能。到此就完成啦
 total = CFG_ENV_SIZE;
 ret = nand_read(&nand_info[0], cfg_env_off, &total, (u_char*)env_ptr);
   if (ret || total != CFG_ENV_SIZE)
 {
  return use_default();
 }
 if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
 {
  return use_default();
 }
#endif /* ! ENV_IS_EMBEDDED */
}
阅读(3080) | 评论(0) | 转发(0) |
0

上一篇:三星s3c6410启动uboot简化版

下一篇:没有了

给主人留下些什么吧!~~