nand_cet.c

来自「This is about correction error table in 」· C语言 代码 · 共 967 行 · 第 1/2 页

C
967
字号
		if (count == 0) {
			/* The global cerr_count is in the 2nd page's OOB area */
			from += this->pageSize;
			if (nand_cet_read_oob(mtd, oobbuf, from)) {
				printk(KERN_ERR "nandCET: %s %d Error reading OOB\n", __FUNCTION__, __LINE__);
				cet->flags = NAND_CET_DISABLED;
				return -1;
			}
			cet->cerr_count = *((uint32_t *) (oobbuf + cet->offs));
			/* TODO - Fix this -> recreate */
			if (cet->cerr_count == 0xffffffff) {
				/* Reset it to 0 */
				cet->cerr_count = 0;
				cet->memtbl[0].isdirty = 1;
			}
			if (unlikely(gdebug)) {
				printk(KERN_INFO "nandCET: correctable error count = %x\n", cet->cerr_count);
			}
			/* If force then go thru all CET blks even if cerr_count is 0 */
			if (!force) {
				if (cet->cerr_count == 0) {
					cet->flags = NAND_CET_LAZY;
					return 0;
				} 
			}
		}
		cet->memtbl[ret].blk = cet->startblk + i*cet->sign;
		count++;
#if 0
		printk(KERN_INFO "DEBUG -> count = %d, nblks = %d blk = %d\n", count, cet->numblks, cet->memtbl[ret].blk);
#endif
		if (count == cet->numblks) {
			cet->flags = NAND_CET_LOADED;
			return 0;
		}
	}
	/* This should never happen */
	cet->flags = NAND_CET_DISABLED;
	return -1;
}

/*
 * flush pending in-memory CET data to the flash. Called as part of a 
 * callback function from workqueue that is invoked every SYNC_FREQ seconds
 */
static int flush_memcet(struct mtd_info *mtd) 
{
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet = this->cet;
	struct erase_info einfo;
	int i, j, k = 0, ret, pg_idx = 0, gdebug = 0;
	uint8_t oobbuf[mtd->oobsize];
	loff_t from, to;
	char *oobptr, count = 0;

	/* If chip is locked reset timer for a later time */
	if (spin_is_locked(&this->chip_lock)) {
		printk(KERN_INFO "DEBUG -> nandCET: flash locked reseting timer\n");
		return -1;
	}
	if (unlikely(gdebug)) {
		printk(KERN_INFO "nandCET: Inside %s\n", __FUNCTION__);
	}
	/* For each in-mem dirty block, sync with flash 
	   sync => erase -> write */
	for (i = 0; i < cet->numblks; i++) {
		if (cet->memtbl[i].isdirty && cet->memtbl[i].blk != -1) {
			/* Erase */
			from = __ll_LeftShift32(cet->memtbl[i].blk, this->bbt_erase_shift);
			to = from;
			memset(&einfo, 0, sizeof(einfo));
			einfo.mtd = mtd;
			einfo.addr = __ll_low(from);
			einfo.len = mtd->erasesize;
			ret = this->erase_bbt(mtd, &einfo, 1, 1);
			if (unlikely(ret < 0)) {
				printk(KERN_ERR "nandCET: %s Error erasing block %x\n", __FUNCTION__, cet->memtbl[i].blk);
				return -1;
			}
			if (unlikely(gdebug)) {
				printk(KERN_INFO "DEBUG -> nandCET: After erasing ...\n");
				cet_printpg_oob(mtd, cet, 3);
			}
			pg_idx = 0;
			/* Write pages i.e., flush */
			for (j = 0; j < mtd->erasesize/this->pageSize; j++) {
				memset(oobbuf, 0xff, mtd->oobsize);
				oobptr = (char *) oobbuf;
				if (j == 0) { /* Write CET# */
					for (k = 0; k < cet->len-1; k++) {
						oobptr[cet->offs + k] = cet->pattern[k];
					}
					oobptr[cet->offs + k] = count;
					if (unlikely(gdebug)) {
						print_oobbuf((const char *) oobbuf, mtd->oobsize);
					}
				}
				if (j == 1 && count == 0) { /* Write cerr_count */
					*((uint32_t *) (oobptr + cet->offs)) = cet->cerr_count;
				}
				ret = nand_cet_write(mtd, to, (size_t) this->pageSize, cet->memtbl[i].bitvec+pg_idx, oobbuf);
				if (ret < 0) {
					printk(KERN_ERR "nandCET: %s Error writing to page %x\n", __FUNCTION__, (unsigned int) to);
					return ret;
				}
				to += mtd->writesize;
				pg_idx += mtd->writesize;
			}
			cet->memtbl[i].isdirty = 0;
			if (unlikely(gdebug)) {
				printk(KERN_INFO "nandCET: flushing CET block %d\n", i);
			}
		}
		count++;
	}

	return 0;
}

/*
 * The callback function for kernel workq task
 * Checks if there is any work to be done, if so calls flush_memcet
 * Resets timer before returning in any case
 */
static void sync_cet(void *ptr_mtd) 
{
	int i;
	struct mtd_info *mtd = (struct mtd_info *) ptr_mtd;
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet = this->cet;

	/* Check if all blocks are clean */
	for (i = 0; i < cet->numblks; i++) {
		if (cet->memtbl[i].isdirty) break;
	}
	/* Avoid function call cost if there are no dirty blocks */
	if (i != cet->numblks)
		flush_memcet(mtd);
	schedule_delayed_work(&cet->cet_flush, CET_SYNC_FREQ);

	return;
}


/*
 * nand_create_cet - Create a CET (Correctable Error Table)
 * @param mtd		MTD device structure
 * 
 * Called during mtd init. Checks if a CET already exists or needs
 * to be created. Initializes in-memory CET. 
 */
int nand_create_cet(struct mtd_info *mtd)
{
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet;
	int gdebug = 0, i, ret;

	if (unlikely(gdebug)) {
		printk(KERN_INFO "nandCET: Creating correctable error table ...\n");
	}
	this->cet = cet = &cet_descr;
	cet->flags = 0x00;
	if (NAND_IS_MLC(this)) {
		printk(KERN_INFO "Disabling CET for MLC NAND\n");
		cet->flags = NAND_CET_DISABLED;
		return -1;
	}
	/* Check that BBT table and mirror exist */
	if (unlikely(!this->bbt_td && !this->bbt_md)) {
		printk(KERN_INFO "nandCET: BBT tables not found, disabling\n");
		cet->flags = NAND_CET_DISABLED;
		return -1;
	}
	/* Per chip not supported. We do not use per chip BBT, but this 
	   is just a safety net */
	if (unlikely(this->bbt_td->options & NAND_BBT_PERCHIP)) {
		printk(KERN_INFO "nandCET: per chip CET not supported, disabling\n");
		cet->flags = NAND_CET_DISABLED;
		return -1;
	}
	/* Calculate max blocks based on 1-bit per page */
	cet->numblks = (this->mtdSize/this->pageSize)/(8*this->blockSize);
	if (((this->mtdSize/this->pageSize)/8)%this->blockSize) {
		cet->numblks++;
	}
	/* Allocate twice the size in case we have bad blocks */
	cet->maxblks = cet->numblks*2;
	/* Determine the direction of CET based on reverse direction of BBT */
	cet->sign = (this->bbt_td->options & NAND_BBT_LASTBLOCK) ? 1 : -1;
	/* For flash size <= 512MB BBT and CET share the last 1MB
	   for flash size > 512MB CET is at the 512th MB of flash */
	if (this->mtdSize < (1<<29)) {
		if (cet->maxblks + BBT_MAX_BLKS > BBT_PARTITION/this->blockSize) {
			printk(KERN_INFO "nandCET: Not enough space to store CET, disabling CET\n");
			cet->flags = NAND_CET_DISABLED;
			return -1;
		}
		if (cet->sign) {
			cet->startblk = CET_START_BLK(this->mtdSize, this)
		} else {
			cet->startblk = __ll_low(__ll_RightShift(this->mtdSize, this->bbt_erase_shift)-1);
		}

	} else {
		if (cet->maxblks > (BBT_PARTITION)/this->blockSize) {
			printk(KERN_INFO "nandCET: Not enough space to store CET, disabling CET\n");
			cet->flags = NAND_CET_DISABLED;
			return -1;
		}
		cet->startblk = CET_START_BLK((1<<29), this)
	}
	if (gdebug) {
		printk(KERN_INFO "nandCET: start blk = %x, numblks = %x\n", cet->startblk, cet->numblks);
	}

	/* Init memory based CET */
	cet->memtbl = (struct nand_cet_memtable *) vmalloc(cet->numblks*sizeof(struct nand_cet_memtable));
	if (cet->memtbl == NULL) {
		printk(KERN_ERR "nandCET: vmalloc failed %s\n", __FUNCTION__);
		cet->flags = NAND_CET_DISABLED;
		return -1;
	}
	for (i = 0; i < cet->numblks; i++) {
		cet->memtbl[i].isdirty = 0;
		cet->memtbl[i].blk = -1;
		cet->memtbl[i].bitvec = NULL;
	}
	ret = search_cet_blks(mtd, cet, 0);
	if (unlikely(gClearCET == 1)) {		/* kernel cmdline showcet */
		cmdline_showcet(mtd, cet);
	}
	if (unlikely(gClearCET == 2)) { 	/* kernel cmdline resetcet */
		if (cmdline_resetcet(mtd, cet) < 0) {
			cet->flags = NAND_CET_DISABLED;
			return -1;
		}
	}
	if (unlikely(gClearCET == 3)) {		/* kernel cmdline disable */
		cet->flags = NAND_CET_DISABLED;
		ret = -1;
	}
	//cet_printpg_oob(mtd, cet, 3);
	switch(cet->flags) {
		case NAND_CET_DISABLED:
			printk(KERN_INFO "nandCET: Status -> Disabled\n");
			break;
		case NAND_CET_LAZY:
			printk(KERN_INFO "nandCET: Status -> Deferred\n");
			break;
		case NAND_CET_LOADED:
			printk(KERN_INFO "nandCET: Status -> Loaded\n");
			break;
		default:
			printk(KERN_INFO "nandCET: Status -> Fatal error CET disabled\n");
			cet->flags = NAND_CET_DISABLED;
			break;
	}
	if (unlikely(gdebug)) {
		cet_printpg_oob(mtd, cet, 3);
		cet_printblk_oob(mtd, cet);
	}

	INIT_WORK(&cet->cet_flush, sync_cet, (void *) mtd);
	schedule_delayed_work(&cet->cet_flush, CET_SYNC_FREQ);

	return ret;
}

/*
 * nand_cet_erasecallback: Called every time there is an erase due to
 *                             userspace activity
 *
 * @param mtd		MTD device structure
 * @param addr		Address of the block that was erased by fs/userspace
 *
 * Assumption: cet->flag != NAND_CET_DISABLED || NAND_CET_LAZY
 * is checked by the caller
 * flag == NAND_CET_DISABLED => CET not being used
 * flag == NAND_CET_LAZY => correctable error count is 0 so need of callback
 * 
 * TODO Optimize, add comments, check all return paths
 */
int nand_cet_erasecallback(struct mtd_info *mtd, u_int32_t addr) 
{
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet = this->cet;
	uint32_t page = 0;
	int blkbegin, blk, i, ret, retlen, pg_idx = 0, numzeros = 0, byte, gdebug = 0;
	uint32_t *ptr;
	unsigned int pos;
	loff_t origaddr = addr;
	
	/* Find out which entry in the memtbl does the addr map to */
	page = __ll_RightShift(addr, this->page_shift);
	blk = page/(this->blockSize<<3);
	if (unlikely(cet->memtbl[blk].blk == -1)) {
		printk(KERN_INFO "nandCET: %s invalid block# in CET\n", __FUNCTION__);
		return -1;
	}
	blkbegin = cet->memtbl[blk].blk;
	/* Start page of the block */
	addr = __ll_LeftShift32(blkbegin, this->bbt_erase_shift);
	if (cet->memtbl[blk].bitvec == NULL) {
		if (gdebug) {
			printk(KERN_INFO "DEBUG -> nandCET: bitvec is null, reloading\n");
		}
		cet->memtbl[blk].bitvec = (char *) vmalloc(this->blockSize);
		if (cet->memtbl[blk].bitvec == NULL) {
			printk(KERN_INFO "nandCET: %s vmalloc failed\n", __FUNCTION__);
			return -1;
		}
		memset(cet->memtbl[blk].bitvec, 0xff, sizeof(this->blockSize));
		/* Read an entire block */
		for (i = 0; i < mtd->erasesize/mtd->writesize; i++) {
			if (gdebug) {
				printk(KERN_INFO "DEBUG -> nandCET: Reading page %d\n", i);
			}
			ret = mtd->read(mtd, addr, this->pageSize, &retlen, (uint8_t *) (cet->memtbl[blk].bitvec+pg_idx));
			if (ret < 0 || (retlen != this->pageSize)) {
				vfree(cet->memtbl[blk].bitvec);
				return -1;
			}
			pg_idx += mtd->writesize;
			addr += this->pageSize;
		}
	} 
	page = (uint32_t) ((origaddr & (~(mtd->erasesize-1))) >> this->page_shift);
	pos = page % (this->blockSize<<3);
	byte = pos / (1<<3);
	ptr = (uint32_t *) ((char *)cet->memtbl[blk].bitvec+byte);
	/* numpages/8bits per byte/4byte per uint32 */
	for (i = 0; i < ((mtd->erasesize/mtd->writesize)>>3)>>2; i++) {
		/* Count the number of 0s for in the bitvec */
		numzeros += bitcount(~ptr[i]);
	}
	if (likely(numzeros == 0)) {
		if (gdebug) {
			printk(KERN_INFO "DEBUG -> nandCET: returning 0 numzeros = 0\n");
		}
		return 0;
	} 
	if (cet->cerr_count < numzeros) {
		if (gdebug) {
			printk(KERN_ERR "nandCET: Erroneous correctable error count");
		}
		return -1;
	}
	cet->cerr_count -= numzeros;
	/* Make bits corresponding to this block all 1s */
	memset(cet->memtbl[blk].bitvec+byte, 0xff, (mtd->erasesize/mtd->writesize)>>3);
	cet->memtbl[blk].isdirty = 1;

	return 0;
}

/*
 * nand_cet_update: Called every time a single correctable error is
 *                      encountered.
 * @param mtd		MTD device structure
 * @param from		Page address at which correctable error occured
 * @param status	Return status 
 *			1 => This page had a correctable errror in past,
 *			therefore, return correctable error to filesystem
 *			0 => First occurence of a correctable error for
 *			this page. return a success to the filesystem
 *  
 * Check the in memory CET bitvector to see if this page (loff_t from)
 * had a correctable error in past, if not set this page's bit to '0'
 * in the bitvector.
 *
 */
int nand_cet_update(struct mtd_info *mtd, loff_t from, int *status) 
{
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet = this->cet;
	int gdebug = 0, ret, blk, byte, bit, retlen = 0, blkbegin, i;
	uint32_t page = 0;
	unsigned int pg_idx = 0, pos = 0;
	unsigned char c, mask;

	if (gdebug) {
		printk(KERN_INFO "DEBUG -> nandCET: Inside %s\n", __FUNCTION__);
	}
	if (cet->flags == NAND_CET_LAZY) {
		/* Force creation of the CET and the mem table */
		ret = search_cet_blks(mtd, cet, 1);
		if (ret < 0) {
			cet->flags = NAND_CET_DISABLED;
			return ret;
		}
		cet->flags = NAND_CET_LOADED;
	}
	/* Find out which entry in memtbl does the from address map to */
	page = __ll_RightShift(from, this->page_shift);
	/* each bit is one page << 3 for 8 bits per byte */
	blk = page/(this->blockSize<<3);
	if (unlikely(cet->memtbl[blk].blk == -1)) {
		printk(KERN_INFO "nandCET: %s invalid block# in CET\n", __FUNCTION__);
		return -1;
	}
	blkbegin = cet->memtbl[blk].blk;
	/* Start page of the block */
	from = __ll_LeftShift32(blkbegin, this->bbt_erase_shift);
	/* If bitvec == NULL, load the block from flash */
	if (cet->memtbl[blk].bitvec == NULL) {
		if (gdebug) {
			printk(KERN_INFO "DEBUG -> nandCET: bitvec null .... loading ...\n");
		}
		cet->memtbl[blk].bitvec = (char *) vmalloc(this->blockSize);
		if (cet->memtbl[blk].bitvec == NULL) {
			printk(KERN_ERR "nandCET: %s vmalloc failed\n", __FUNCTION__);
			return -1;
		}
		memset(cet->memtbl[blk].bitvec, 0xff, this->blockSize);
		/* Read an entire block */
		if (gdebug) {
			printk(KERN_INFO "DEBUG -> nandCET: Reading pages starting @ %x\n", (unsigned int) from);
		}
		for (i = 0; i < mtd->erasesize/mtd->writesize; i++) {
			ret = mtd->read(mtd, from, this->pageSize, &retlen, (uint8_t *) (cet->memtbl[blk].bitvec+pg_idx));
			if (ret < 0 || (retlen != this->pageSize)) {
				vfree(cet->memtbl[blk].bitvec);
				return -1;
			}
			pg_idx += mtd->writesize;
			from += this->pageSize;
		}
	}
	pos = page % (this->blockSize<<3);
	byte = pos / (1<<3);
	bit = pos % (1<<3);
	c = cet->memtbl[blk].bitvec[byte];
	mask = 1<<bit;
	if ((c & mask) == mask) { /* First time error mark it but return a good status */
		*status = 0;
		c = (c & ~mask);
		cet->memtbl[blk].bitvec[byte] = c;
		cet->memtbl[blk].isdirty = 1;
	} else {		
		*status = 1; /* This page had a previous error so return a bad status */
	}
	cet->cerr_count++;
#if 0
	printk(KERN_INFO "DEBUG -> count = %d, byte = %d, bit = %d, blk = %x status = %d c = %d addr = %x\n", cet->cerr_count\
			, byte, bit, blk, *status, cet->memtbl[blk].bitvec[byte], cet->memtbl[blk].bitvec+byte);
	printk(KERN_INFO "DEBUG -> CET: Exiting %s\n", __FUNCTION__);
#endif

	return 0;
}

/*
 * nand_cet_prepare_reboot Call flush_memcet to flush any in-mem dirty data
 * 
 * @param mtd		MTD device structure
 *
 * Flush any pending in-mem CET blocks to flash before reboot 
 */
int nand_cet_prepare_reboot(struct mtd_info *mtd) 
{
	int gdebug = 1;
	struct nand_chip *this = (struct nand_chip *) mtd->priv;
	struct nand_cet_descr *cet = this->cet;

	// Disable for MLC
	if (NAND_IS_MLC(this)) {
		return 0;
	}
	if (unlikely(gdebug)) {
		uart_puts(KERN_INFO "DEBUG -> nandCET: flushing pending CET\n");
	}
	if (unlikely(cet->flags == NAND_CET_DISABLED)) {
		return 0;
	}
	flush_memcet(mtd);

	return 0;
}

EXPORT_SYMBOL(nand_cet_update);

#endif

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?