大家都知道,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 */
}
阅读(3139) | 评论(0) | 转发(0) |