[U-Boot] [PATCH v2] nand: Hack to support 4k page in fsl_elbc_nand

Scott Wood scottwood at freescale.com
Fri Jun 29 03:36:35 CEST 2012


On 06/28/2012 03:47 PM, Rafael Beims wrote:
> Freescale FCM controller has a 2K size limitation of buffer RAM. In order
> to support the Nand flash chip with pagesize larger than 2K bytes,
> we read/write 2k data repeatedly by issuing FIR_OP_RB/FIR_OP_WB and save
> them to a large buffer.
> Because of this, the in flash layout of the oob is different from the
> default for 4096kiB page sizes. Therefore, we need to migrate the
> factory bad block markers from the original position to the new layout.
> 
> Signed-off-by: Shengzhou Liu <Shengzhou.Liu at freescale.com>
> Signed-off-by: Liu Shuo <b35362 at freescale.com>
> Signed-off-by: Rafael Beims <rafael.beims at datacom.ind.br>
> ---
> Changes in v2:
>  - Added check to disallow the migration code to run in devices with
>  page size <= 2048
> 
>  drivers/mtd/nand/fsl_elbc_nand.c |  447 +++++++++++++++++++++++++++++++++++---
>  1 files changed, 419 insertions(+), 28 deletions(-)

Thanks for working on this!  I've been meaning to for a while, but have
been busy with other things.

> diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c
> index 9076ad4..3b6bb1d 100644
> --- a/drivers/mtd/nand/fsl_elbc_nand.c
> +++ b/drivers/mtd/nand/fsl_elbc_nand.c
> @@ -76,15 +76,17 @@ struct fsl_elbc_ctrl {
> 
>  	/* device info */
>  	fsl_lbc_t *regs;
> -	u8 __iomem *addr;        /* Address of assigned FCM buffer        */
> -	unsigned int page;       /* Last page written to / read from      */
> -	unsigned int read_bytes; /* Number of bytes read during command   */
> -	unsigned int column;     /* Saved column from SEQIN               */
> -	unsigned int index;      /* Pointer to next byte to 'read'        */
> -	unsigned int status;     /* status read from LTESR after last op  */
> -	unsigned int mdr;        /* UPM/FCM Data Register value           */
> -	unsigned int use_mdr;    /* Non zero if the MDR is to be set      */
> -	unsigned int oob;        /* Non zero if operating on OOB data     */
> +	u8 __iomem *addr;           /* Address of assigned FCM buffer       */
> +	unsigned int page;          /* Last page written to / read from     */
> +	unsigned int read_bytes;    /* Number of bytes read during command  */
> +	unsigned int column;        /* Saved column from SEQIN              */
> +	unsigned int index;         /* Pointer to next byte to 'read'       */
> +	unsigned int status;        /* status read from LTESR after last op */
> +	unsigned int mdr;           /* UPM/FCM Data Register value          */
> +	unsigned int use_mdr;       /* Non zero if the MDR is to be set     */
> +	unsigned int oob;           /* Non zero if operating on OOB data    */
> +	char *buffer;               /* Just used when pagesize is greater   */
> +				    /* than FCM RAM 2K limitation           */
>  };
> 
>  /* These map to the positions used by the FCM hardware ECC generator */
> @@ -131,6 +133,15 @@ static struct nand_bbt_descr largepage_memorybased = {
>  	.pattern = scan_ff_pattern,
>  };
> 
> +static u8 migrated_pattern[] = { 0xde, 0xad, 0xde, 0xad };

Let's generate a proper random number here -- or at least a meaningful
ASCII string.   Things like the above are too common and invite magic
number collision.

> +static int fsl_elbc_migrate_badblocks(struct mtd_info *mtd,
> +				      struct nand_bbt_descr *bd)
> +{
> +	struct nand_chip *this = mtd->priv;
> +	int len, numblocks, i;
> +	int startblock;
> +	loff_t from;
> +	uint8_t *newoob, *buf;
> +
> +	struct fsl_elbc_mtd *priv = this->priv;
> +	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
> +
> +	int num_subpages = mtd->writesize / 2048-1;
> +	len = mtd->writesize + mtd->oobsize;
> +	numblocks = this->chipsize >> (this->phys_erase_shift - 1);
> +	startblock = 0;
> +	from = 0;
> +
> +	newoob = vmalloc(mtd->oobsize);

Even if this were Linux, oobsize should never be big enough to need vmalloc.

> +	memset(newoob, 0xff, mtd->writesize+mtd->oobsize);

You're writing beyond the end of that buffer.

> +	for (i = startblock; i < numblocks; i += 2) {
> +		int page = (from >> this->page_shift) & this->pagemask;
> +		fsl_elbc_cmdfunc(mtd, NAND_CMD_READ0, 0, page);
> +
> +		/* As we are already using the hack to read the bytes
> +		 * from NAND, the original badblock marker is offset
> +		 * from its original location in the internal buffer.
> +		 * This is because the hack reads 2048 + 64 and already
> +		 * positions the spare in the correct region
> +		 * (after the 4096 offset)
> +		 */
> +		uint8_t *badblock_pattern = (ctrl->buffer+
> +				mtd->writesize-(num_subpages*64))+bd->offs;

Spaces around operators.

I think that should be (num_subpages - 1) * 64.

> +		if (fsl_elbc_check_pattern(mtd, badblock_pattern, bd)) {
> +			printf("Badblock found at %08x, migrating...\n", page);
> +			memcpy(newoob, badblock_pattern , bd->len);

No space before ,

> +static int fsl_elbc_write_migration_marker(struct mtd_info *mtd,
> +			uint8_t *buf, int len, struct nand_bbt_descr *bd)
> +{
> +	struct nand_chip *this = mtd->priv;
> +	struct nand_bbt_descr *td = this->bbt_td;
> +	int startblock;
> +	int dir, i;
> +	int blocks_to_write = 2;
> +
> +	/* Start below maximum bbt */
> +	startblock = (mtd->size >> this->phys_erase_shift) - td->maxblocks;
> +	dir = -1;

If you want startblock below the bbt area, I think you're off by one.

> +	for (i = 0; i < bd->maxblocks && blocks_to_write != 0; i++) {
> +		int actblock = startblock + dir*i;
> +		loff_t offs = (loff_t)actblock << this->phys_erase_shift;
> +		int page = (offs >> this->page_shift) & this->pagemask;
> +
> +		/* Avoid badblocks writing the marker... */
> +		if (!fsl_elbc_scan_read_raw_oob(mtd, buf, page, mtd->writesize,
> +					&largepage_memorybased)) {
> +
> +			/* We are reusing this buffer, reset it */
> +			memset(buf, 0xff, len);
> +			memcpy(buf+bd->offs, bd->pattern, bd->len);
> +
> +			/*  Mark the block as bad the same way that
> +			 *  it's done for the bbt. This should avoid
> +			 *  this block being overwritten
> +			 */
> +			memset(buf+this->badblockpos, 0x02, 1);
> +
> +			fsl_elbc_cmdfunc(mtd, NAND_CMD_SEQIN,
> +						mtd->writesize, page);
> +			fsl_elbc_write_buf(mtd, buf, mtd->oobsize);
> +			fsl_elbc_cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
> +			blocks_to_write--;
> +			printf("Wrote migration marker to offset: %x\n", page);
> +		}

Why are we writing the marker twice?

> +static int fsl_elbc_scan_bbt(struct mtd_info *mtd)
> +{
> +	struct nand_chip *this = mtd->priv;
> +	struct nand_bbt_descr *bd = &largepage_migrated;
> +	struct nand_bbt_descr *td = this->bbt_td;
> +	int len;
> +	int startblock, block, dir;
> +	uint8_t *buf;
> +	int migrate = 0;
> +
> +	if (mtd->writesize > 2048) {
> +		/* Start below maximum bbt */
> +		startblock = (mtd->size >> this->phys_erase_shift) - td->maxblocks;

Again, off by one.

> +		dir = -1;

dir never seems to get set to anything else, so why a variable?

I think we should start with the last block, and search backwards until
we find it, or until we exceed some reasonable limit, such as
td->maxblocks * 2 (Is that really enough?  How many contiguous bad
blocks can we see?).  Actually writing a joint bbt/migration marker
(which was requested in earlier discussion to avoid wasting an erase
block, but I don't want it to be mandatory) can come later, but we want
to recognize it now.

> +		/* Allocate a temporary buffer for one eraseblock incl. oob */
> +		len = (1 << this->phys_erase_shift);
> +		len += (len >> this->page_shift) * mtd->oobsize;
> +		buf = vmalloc(len);

This code isn't going to be shared with Linux, so just malloc().
Likewise printf(...) instead of printk(KERN_ERR ...).

BTW, should mention at least in the changelog that this is partially
derived from code in nand_bbt.c.  Also, using this code means we need to
remove the "or later" from fsl_elbc_nand.c, because nand_bbt.c is GPL v2
only.  Wolfgang probably won't be pleased.  It might be better to just
write it from scratch -- I'm not sure how much it really helps to be
mimicking the bbt code here (without actually being able to share it),
versus straightforwardly coding this specific task.

> +		if (!buf) {
> +			printk(KERN_ERR "fsl_elbc_nand: Out of memory\n");
> +			kfree(this->bbt);
> +			this->bbt = NULL;
> +			return -ENOMEM;
> +		}

Why are we freeing the bbt here?  When did we allocate it?

> +		for (block = 0; block < td->maxblocks; block++) {
> +			int actblock = startblock + dir * block;
> +			loff_t offs = (loff_t)actblock << this->phys_erase_shift;
> +			int page = (offs >> this->page_shift) & this->pagemask;
> +
> +			migrate = fsl_elbc_scan_read_raw_oob(mtd, buf, page,
> +					mtd->writesize, &largepage_migrated);
> +
> +			/* We found the migration marker, get out of here */
> +			if (migrate == 0)
> +				break;
> +		}
> +
> +		if (migrate) {
> +			printf("Moving factory marked badblocks to new oob\n");
> +			fsl_elbc_migrate_badblocks(mtd, &largepage_memorybased);
> +			fsl_elbc_write_migration_marker(mtd, buf, len,
> +							&largepage_migrated);
> +		}
> +
> +		vfree(buf);
> +	}
> +	/* Now that we checked and possibly migrated badblock
> +	 * markers, continue with default bbt scanning */

/*
 * U-Boot multiline comment style
 * is like this.
 */

Again, thanks for working on this.  To properly test it I need to get
raw reads/writes working properly with eLBC, though.  Once I do that,
I'll fix this patch up (unless you want to do a v3 before then).

-Scott



More information about the U-Boot mailing list