OneNAND: Bad block aware read/write command support
PATCH v2 1/1 OneNAND: Bad block aware read/write command support
Update OneNAND command to support bad block awareness. Also change the OneNAND command style to better match the NAND version.
---
v2:
- Removed the bad block marking when an erase/read/write access to a block fails
- More cleanup's
common/cmd_onenand.c | 529 +++++++++++++++++++++++++++++++++++++++----------- 1 files changed, 419 insertions(+), 110 deletions(-)
common/cmd_onenand.c +419 -110
@@ -1,7 +1,7 @@/* * U-Boot command for OneNAND support * -* Copyright (C) 2005-2007 Samsung Electronics +* Copyright (C) 2005-2008 Samsung Electronics * Kyungmin Park <[email protected]> * * This program is free software; you can redistribute it and/or modify
@@ -11,6 +11,7 @@#include <common.h> #include <command.h> +#include <malloc.h> #include <linux/mtd/compat.h> #include <linux/mtd/mtd.h>
@@ -18,159 +19,467 @@#include <asm/io.h> -extern struct mtd_info onenand_mtd; -extern struct onenand_chip onenand_chip; +static struct mtd_info *mtd = &onenand_mtd; + +static loff_t next_ofs; +static loff_t skip_ofs; + +static inline int str2long (char *p,ulong *num) +{ +char *endptr; + + *num =simple_strtoul (p, &endptr,16 ); +return (*p !='\0' && *endptr =='\0' ) ?1 :0 ; +} + +static int arg_off_size (int argc,char *argv[],ulong *off,size_t *size) +{ +if (argc >=1 ) { +if (!(str2long (argv[0 ], off))) { +printf ("'%s' is not a number\n" , argv[0 ]); +return -1 ; + } + }else { + *off =0 ; + } + +if (argc >=2 ) { +if (!(str2long (argv[1 ], (ulong *)size))) { +printf ("'%s' is not a number\n" , argv[1 ]); +return -1 ; + } + }else { + *size = mtd->size - *off; + } + +if ((*off + *size) > mtd->size) { +printf ("total chip size (0x%x) exceeded!\n" , mtd->size); +return -1 ; + } + +if (*size == mtd->size) +puts ("whole chip\n" ); +else +printf ("offset 0x%lx, size 0x%x\n" , *off, *size); + +return 0 ; +} + +static int onenand_block_read (loff_t from,size_t len, +size_t *retlen,u_char *buf,int oob) +{ +struct onenand_chip *this = mtd->priv; +int blocks = (int ) len >> this->erase_shift; +int blocksize = (1 << this->erase_shift); +loff_t ofs = from; +struct mtd_oob_ops ops = { + .retlen =0 , + }; +int ret, dump =0 ; + +if (oob) + ops.ooblen = blocksize; +else + ops.len = blocksize; + +while (blocks) { + ret = mtd->block_isbad (mtd, ofs); +if (ret) { +printk ("Bad blocks %d at 0x%x\n" , + (u32)(ofs >> this->erase_shift), (u32 )ofs); + ofs += blocksize; +continue ; + } + +if (oob) + ops.oobbuf = buf; +else + ops.datbuf = buf; + + ops.retlen =0 ; + ret = mtd->read_oob (mtd, ofs, &ops); +if (ret) { +printk ("Read failed 0x%x, %d" , (u32 )ofs, ret); + ofs += blocksize; +continue ; + } + ofs += blocksize; + buf += blocksize; + blocks--; + *retlen += ops.retlen; + } + +return 0 ; +} + +static int onenand_block_write (loff_t to,size_t len, +size_t *retlen,const u_char * buf) +{ +struct onenand_chip *this = mtd->priv; +int blocks = len >> this->erase_shift; +int blocksize = (1 << this->erase_shift); +loff_t ofs; +size_t _retlen =0 ; +int ret; + +if (to == next_ofs) { + next_ofs = to + len; + to += skip_ofs; + }else { + next_ofs = to + len; + skip_ofs =0 ; + } + ofs = to; + +while (blocks) { + ret = mtd->block_isbad (mtd, ofs); +if (ret) { +printk ("Bad blocks %d at 0x%x\n" , + (u32)(ofs >> this->erase_shift), (u32 )ofs); + skip_ofs += blocksize; + goto next; + } + + ret = mtd->write (mtd, ofs, blocksize, &_retlen, buf); +if (ret) { +printk ("Write failed 0x%x, %d" , (u32 )ofs, ret); + skip_ofs += blocksize; + goto next; + } + + buf += blocksize; + blocks--; + *retlen += _retlen; +next: + ofs += blocksize; + } + +return 0 ; +} + +static int onenand_block_erase (u32 start,u32 size,int force) +{ +struct onenand_chip *this = mtd->priv; +struct erase_info instr = { + .callback =NULL , + }; +loff_t ofs; +int ret; +int blocksize =1 << this->erase_shift; + +for (ofs = start; ofs < (start + size); ofs += blocksize) { + ret = mtd->block_isbad (mtd, ofs); +if (ret && !force) { +printf ("Skip erase bad block %d at 0x%x\n" , + (u32)(ofs >> this->erase_shift), (u32 )ofs); +continue ; + } + + instr.addr = ofs; + instr.len = blocksize; + instr.priv = force; + ret = mtd->erase (mtd, &instr); +if (ret) { +printf ("erase failed block %d at 0x%x\n" , + (u32)(ofs >> this->erase_shift), (u32 )ofs); +continue ; + } + } + +return 0 ; +} + +static int onenand_block_test (u32 start,u32 size) +{ +struct onenand_chip *this = mtd->priv; +struct erase_info instr = { + .callback =NULL , + .priv =0 , + }; + +int blocks; +loff_t ofs; +int blocksize =1 << this->erase_shift; +int start_block, end_block; +size_t retlen; +u_char *buf; +u_char *verify_buf; +int ret; + + buf =malloc (blocksize); +if (!buf) { +printf ("Not enough malloc space available!\n" ); +return -1 ; + } + + verify_buf =malloc (blocksize); +if (!verify_buf) { +printf ("Not enough malloc space available!\n" ); +return -1 ; + } + + start_block = start >> this->erase_shift; + end_block = (start + size) >> this->erase_shift; + +/* Protect boot-loader from badblock testing */ +if (start_block <2 ) + start_block =2 ; + +if (end_block > (mtd->size >> this->erase_shift)) + end_block = mtd->size >> this->erase_shift; + + blocks = start_block; + ofs = start; +while (blocks < end_block) { +printf ("\rTesting block %d@0x%x" , (u32)(ofs >> this->erase_shift), (u32 )ofs); + + ret = mtd->block_isbad (mtd, ofs); +if (ret) { +printf ("Skip erase bad block %d at 0x%x\n" , + (u32)(ofs >> this->erase_shift), (u32 )ofs); + goto next; + } + + instr.addr = ofs; + instr.len = blocksize; + ret = mtd->erase (mtd, &instr); +if (ret) { +printk ("Erase failed 0x%x, %d" , (u32 )ofs, ret); + goto next; + } + + ret = mtd->write (mtd, ofs, blocksize, &retlen, buf); +if (ret) { +printk ("Write failed 0x%x, %d" , (u32 )ofs, ret); + goto next; + } + + ret = mtd->read (mtd, ofs, blocksize, &retlen, verify_buf); +if (ret) { +printk ("Read failed 0x%x, %d" , (u32 )ofs, ret); + goto next; + } + +if (memcmp (buf, verify_buf, blocksize)) +printk ("\nRead/Write test failed at 0x%x\n" , (u32 )ofs); + +next: + ofs += blocksize; + blocks++; + } +printf ("...Done\n" ); + +free (buf); +free (verify_buf); + +return 0 ; +} + +static int onenand_dump (struct mtd_info *mtd,ulong off,int only_oob) +{ +int i; +u_char *datbuf, *oobbuf, *p; +struct mtd_oob_ops ops; +loff_t addr; + + datbuf =malloc (mtd->writesize + mtd->oobsize); + oobbuf =malloc (mtd->oobsize); +if (!datbuf || !oobbuf) { +puts ("No memory for page buffer\n" ); +return 1 ; + } + off &= ~(mtd->writesize -1 ); + addr = (loff_t ) off; +memset (&ops,0 ,sizeof (ops)); + ops.datbuf = datbuf; + ops.oobbuf = oobbuf;/* must exist, but oob data will be appended to ops.datbuf */ + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.retlen =0 ; + i = mtd->read_oob (mtd, addr, &ops); +if (i <0 ) { +printf ("Error (%d) reading page %08lx\n" , i, off); +free (datbuf); +free (oobbuf); +return 1 ; + } +printf ("Page %08lx dump:\n" , off); + i = mtd->writesize >>4 ; + p = datbuf; + +while (i--) { +if (!only_oob) +printf ("\t%02x %02x %02x %02x %02x %02x %02x %02x" +" %02x %02x %02x %02x %02x %02x %02x %02x\n" , + p[0 ], p[1 ], p[2 ], p[3 ], p[4 ], p[5 ], p[6 ], p[7 ], + p[8 ], p[9 ], p[10 ], p[11 ], p[12 ], p[13 ], p[14 ], + p[15 ]); + p +=16 ; + } +puts ("OOB:\n" ); + i = mtd->oobsize >>3 ; +while (i--) { +printf ("\t%02x %02x %02x %02x %02x %02x %02x %02x\n" , + p[0 ], p[1 ], p[2 ], p[3 ], p[4 ], p[5 ], p[6 ], p[7 ]); + p +=8 ; + } +free (datbuf); +free (oobbuf); + +return 0 ; +}int do_onenand (cmd_tbl_t * cmdtp,int flag,int argc,char *argv[]) { -int ret =0 ; +struct onenand_chip *this; +int blocksize; +ulong addr, ofs; +size_t len, retlen =0 ; +int ret; +char *cmd, *s; + + mtd = &onenand_mtd; + this = mtd->priv; + blocksize = (1 << this->erase_shift); + + cmd = argv[1 ];switch (argc) {case 0 :case 1 : -printf ("Usage:\n%s\n" , cmdtp->usage); -return 1 ; + goto usage;case 2 : -if (strncmp (argv[1 ],"open" ,4 ) ==0 ) { -onenand_init (); +if (strcmp (cmd,"info" ) ==0 ) { +printf ("%s\n" , mtd->name); +return 0 ; + } + +if (strcmp (cmd,"bad" ) ==0 ) { +/* Currently only one OneNAND device is supported */ +printf ("\nDevice %d bad blocks:\n" ,0 ); +for (ofs =0 ; ofs < mtd->size; ofs += mtd->erasesize) { +if (mtd->block_isbad (mtd, ofs)) +printf (" %08x\n" , (u32 )ofs); + } +return 0 ; } -printf ("%s\n" , onenand_mtd.name); -return 0 ;default :/* At least 4 args */ -if (strncmp (argv[1 ],"erase" ,5 ) ==0 ) { -struct erase_info instr = { - .callback =NULL , - }; -ulong start, end; -ulong block; -char *endtail; - -if (strncmp (argv[2 ],"block" ,5 ) ==0 ) { - start =simple_strtoul (argv[3 ],NULL ,10 ); - endtail =strchr (argv[3 ],'-' ); - end =simple_strtoul (endtail +1 ,NULL ,10 ); - }else { - start =simple_strtoul (argv[2 ],NULL ,10 ); - end =simple_strtoul (argv[3 ],NULL ,10 ); - start >>= onenand_chip.erase_shift; - end >>= onenand_chip.erase_shift; -/* Don't include the end block */ - end--; - } +/* +* Syntax is: +* 0 1 2 3 4 +* onenand erase [force] [off size] +*/ +if ((strcmp (cmd,"erase" ) ==0 ) || (strcmp (cmd,"test" ) ==0 )) { +int force = argc >2 && !strcmp ("force" , argv[2 ]); +int o = force ?3 :2 ; +int erase; -if (!end || end <0 ) - end = start; + erase =strcmp (cmd,"erase" ) ==0 ;/* 1 = erase, 0 = test */ +printf ("\nOneNAND %s: " , erase ?"erase" :"test" ); -printf ("Erase block from %lu to %lu\n" , start, end); +/* skip first two or three arguments, look for offset and size */ +if (arg_off_size (argc - o, argv + o, &ofs, &len) !=0 ) +return 1 ; -for (block = start; block <= end; block++) { - instr.addr = block << onenand_chip.erase_shift; - instr.len =1 << onenand_chip.erase_shift; - ret =onenand_erase (&onenand_mtd, &instr); -if (ret) { -printf ("erase failed %lu\n" , block); -break ; - } - } +if (erase) + ret =onenand_block_erase (ofs, len, force); +else + ret =onenand_block_test (ofs, len); -return 0 ; +printf ("%s\n" , ret ?"ERROR" :"OK" ); + +return ret ==0 ?0 :1 ; } -if (strncmp (argv[1 ],"read" ,4 ) ==0 ) { -ulong addr =simple_strtoul (argv[2 ],NULL ,16 ); -ulong ofs =simple_strtoul (argv[3 ],NULL ,16 ); -size_t len =simple_strtoul (argv[4 ],NULL ,16 ); -int oob =strncmp (argv[1 ],"read.oob" ,8 ) ?0 :1 ; -struct mtd_oob_ops ops; +if (strncmp (cmd,"read" ,4 ) ==0 ||strncmp (cmd,"write" ,5 ) ==0 ) { +int read; +int oob =0 ; - ops.mode =MTD_OOB_PLACE ; +if (argc <4 ) + goto usage; -if (oob) { - ops.len =0 ; - ops.datbuf =NULL ; - ops.ooblen = len; - ops.oobbuf = (u_char *) addr; - }else { - ops.len = len; - ops.datbuf = (u_char *) addr; - ops.ooblen =0 ; - ops.oobbuf =NULL ; - } - ops.retlen = ops.oobretlen =0 ; + addr = (ulong )simple_strtoul (argv[2 ],NULL ,16 ); - onenand_mtd.read_oob (&onenand_mtd, ofs, &ops); -printf ("Done\n" ); + read =strncmp (cmd,"read" ,4 ) ==0 ;/* 1 = read, 0 = write */ +printf ("\nOneNAND %s: " , read ?"read" :"write" ); +if (arg_off_size (argc -3 , argv +3 , &ofs, &len) !=0 ) +return 1 ; -return 0 ; - } + s =strchr (cmd,'.' ); +if ((s !=NULL ) && (!strcmp (s,".oob" ))) + oob =1 ; -if (strncmp (argv[1 ],"write" ,5 ) ==0 ) { -ulong addr =simple_strtoul (argv[2 ],NULL ,16 ); -ulong ofs =simple_strtoul (argv[3 ],NULL ,16 ); -size_t len =simple_strtoul (argv[4 ],NULL ,16 ); -size_t retlen =0 ; +if (read) { + ret =onenand_block_read (ofs, len, &retlen, + (u8 *)addr, oob); + }else { + ret =onenand_block_write (ofs, len, &retlen, + (u8 *)addr); + } -onenand_write (&onenand_mtd, ofs, len, &retlen, - (u_char *) addr); -printf ("Done\n" ); +printf (" %d bytes %s: %s\n" , retlen, + read ?"read" :"written" , ret ?"ERROR" :"OK" ); -return 0 ; +return ret ==0 ?0 :1 ; } -if (strncmp (argv[1 ],"block" ,5 ) ==0 ) { -ulong addr =simple_strtoul (argv[2 ],NULL ,16 ); -ulong block =simple_strtoul (argv[3 ],NULL ,10 ); -ulong page =simple_strtoul (argv[4 ],NULL ,10 ); -size_t len =simple_strtol (argv[5 ],NULL ,10 ); -ulong ofs; -int oob =strncmp (argv[1 ],"block.oob" ,9 ) ?0 :1 ; -struct mtd_oob_ops ops; - - ops.mode =MTD_OOB_PLACE ; +if (strcmp (cmd,"markbad" ) ==0 ) { + addr = (ulong )simple_strtoul (argv[2 ],NULL ,16 ); +int ret = mtd->block_markbad (mtd, addr); +if (ret ==0 ) { +printf ("block 0x%08lx successfully marked as bad\n" , + (ulong ) addr); +return 0 ; + }else { +printf ("block 0x%08lx NOT marked as bad! ERROR %d\n" , + (ulong ) addr, ret); + } +return 1 ; + } - ofs = block << onenand_chip.erase_shift; -if (page) - ofs += page << onenand_chip.page_shift; +if (strncmp (cmd,"dump" ,4 ) ==0 ) { +if (argc <3 ) + goto usage; -if (!len) { -if (oob) - ops.ooblen =64 ; -else - ops.len =512 ; - } + s =strchr (cmd,'.' ); + ofs = (int )simple_strtoul (argv[2 ],NULL ,16 ); -if (oob) { - ops.datbuf =NULL ; - ops.oobbuf = (u_char *) addr; - }else { - ops.datbuf = (u_char *) addr; - ops.oobbuf =NULL ; - } - ops.retlen = ops.oobretlen =0 ; +if (s !=NULL &&strcmp (s,".oob" ) ==0 ) + ret =onenand_dump (mtd, ofs,1 ); +else + ret =onenand_dump (mtd, ofs,0 ); -onenand_read_oob (&onenand_mtd, ofs, &ops); -return 0 ; +return ret ==0 ?1 :0 ; }break ; }return 0 ; + +usage: +printf ("Usage:\n%s\n" , cmdtp->usage); +return 1 ; }U_BOOT_CMD ( onenand,6 ,1 , do_onenand,"onenand - OneNAND sub-system\n" , -"info - show available OneNAND devices\n" -"onenand read[.oob] addr ofs len - read data at ofs with len to addr\n" -"onenand write addr ofs len - write data at ofs with len from addr\n" -"onenand erase saddr eaddr - erase block start addr to end addr\n" -"onenand block[.oob] addr block [page] [len] - " -"read data with (block [, page]) to addr" +"info - show available OneNAND devices\n" +"onenand bad - show bad blocks\n" +"onenand read[.oob] addr off size\n" +"onenand write[.oob] addr off size\n" +" read/write 'size' bytes starting at offset 'off'\n" +" to/from memory address 'addr', skipping bad blocks.\n" +"onenand erase [force] [off size] - erase 'size' bytes from\n" +"onenand test [off size] - test 'size' bytes from\n" +" offset 'off' (entire device if not specified)\n" +"onenand dump[.oob] off - dump page\n" +"onenand markbad off - mark bad block at offset (UNSAFE)\n" );
Signature · 1 line
1.6.0.3
PATCH v2 1/1 OneNAND: Bad block aware read/write command support
Quoted 3 lines · Stefan Roese
Update OneNAND command to support bad block awareness. Also change the OneNAND command style to better match the NAND version.
Is there any chance that we could merge this code with cmd_nand.c?
Quoted 1 line
+static struct
mtd_info*mtd =&onenand_mtd;
Will this cause relocation problems on some architectures? Could you have more than one OneNAND on a board?
-Scott
PATCH v2 1/1 OneNAND: Bad block aware read/write command support
Quoted 6 lines · Scott Wood
Quoted 3 lines · Stefan Roese
Update OneNAND command to support bad block awareness. Also change the OneNAND command style to better match the NAND version.
Is there any chance that we could merge this code with
cmd_nand.c?
Possibly. I'm not sure yet. I'll try to investigate this next week.
Quoted 3 lines
Quoted 1 line
+static struct
mtd_info*mtd =&onenand_mtd;Will this cause relocation problems on some architectures?
Yes, you're correct. I missed removing this assignment here. do_onenand() does it correctly though. I'll fix it in the next patch version.
Quoted 1 line
Could you have more than one OneNAND on a board?
No, currently only one OneNAND device is supported. We will add this multi-OneNAND support later.
Best regards,
Stefan
=====================================================================
DENX Software Engineering GmbH, MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-0 Fax: +49-8142-66989-80 Email: office at denx.de
=====================================================================