misc: apanic: bad block handling

Add bad block handling in apanic

Signed-off-by: Tom Zhu <ling.zhu@motorola.com>
Signed-off-by: San Mehat <san@google.com>

misc: apanic: Improved bad-block / watchdog handling

1. handle cases that there is no more good blocks
2. touch softlockup watchdog at the start of apanic
3. change unsigned char get_bb() to unsigned int get_bb()
4. return idx instead of rc2, to keep the previous written pages.

Signed-off-by: Tom Zhu <ling.zhu@motorola.com>
Signed-off-by: San Mehat <san@google.com>
diff --git a/drivers/misc/apanic.c b/drivers/misc/apanic.c
index 2f858be..f50881b 100644
--- a/drivers/misc/apanic.c
+++ b/drivers/misc/apanic.c
@@ -54,8 +54,6 @@
 	u32 threads_length;
 };
 
-#define CHECK_BB	0
-
 struct apanic_data {
 	struct mtd_info		*mtd;
 	struct panic_header	curr;
@@ -68,6 +66,72 @@
 static struct work_struct proc_removal_work;
 static DEFINE_MUTEX(drv_mutex);
 
+static unsigned int *apanic_bbt;
+static unsigned int apanic_erase_blocks;
+static unsigned int apanic_good_blocks;
+
+static void set_bb(unsigned int block, unsigned int *bbt)
+{
+	unsigned int flag = 1;
+
+	BUG_ON(block >= apanic_erase_blocks);
+
+	flag = flag << (block%32);
+	apanic_bbt[block/32] |= flag;
+	apanic_good_blocks--;
+}
+
+static unsigned int get_bb(unsigned int block, unsigned int *bbt)
+{
+	unsigned int flag;
+
+	BUG_ON(block >= apanic_erase_blocks);
+
+	flag = 1 << (block%32);
+	return apanic_bbt[block/32] & flag;
+}
+
+static void alloc_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+	int bbt_size;
+	apanic_erase_blocks = (mtd->size)>>(mtd->erasesize_shift);
+	bbt_size = (apanic_erase_blocks+32)/32;
+
+	apanic_bbt = kmalloc(bbt_size*4, GFP_KERNEL);
+	memset(apanic_bbt, 0, bbt_size*4);
+	apanic_good_blocks = apanic_erase_blocks;
+}
+static void scan_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+	int i;
+
+	for (i = 0; i < apanic_erase_blocks; i++) {
+		if (mtd->block_isbad(mtd, i*mtd->erasesize))
+			set_bb(i, apanic_bbt);
+	}
+}
+
+#define APANIC_INVALID_OFFSET 0xFFFFFFFF
+
+static unsigned int phy_offset(struct mtd_info *mtd, unsigned int offset)
+{
+	unsigned int logic_block = offset>>(mtd->erasesize_shift);
+	unsigned int phy_block;
+	unsigned good_block = 0;
+
+	for (phy_block = 0; phy_block < apanic_erase_blocks; phy_block++) {
+		if (!get_bb(phy_block, apanic_bbt))
+			good_block++;
+		if (good_block == (logic_block + 1))
+			break;
+	}
+
+	if (good_block != (logic_block + 1))
+		return APANIC_INVALID_OFFSET;
+
+	return offset + ((phy_block-logic_block)<<mtd->erasesize_shift);
+}
+
 static void apanic_erase_callback(struct erase_info *done)
 {
 	wait_queue_head_t *wait_q = (wait_queue_head_t *) done->priv;
@@ -117,10 +181,17 @@
 	page_no = (file_offset + offset) / ctx->mtd->writesize;
 	page_offset = (file_offset + offset) % ctx->mtd->writesize;
 
+
+	if (phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize))
+		== APANIC_INVALID_OFFSET) {
+		pr_err("apanic: reading an invalid address\n");
+		mutex_unlock(&drv_mutex);
+		return -EINVAL;
+	}
 	rc = ctx->mtd->read(ctx->mtd,
-			    (page_no * ctx->mtd->writesize),
-			    ctx->mtd->writesize,
-			    &len, ctx->bounce);
+		phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize)),
+		ctx->mtd->writesize,
+		&len, ctx->bounce);
 
 	if (page_offset)
 		count -= page_offset;
@@ -153,14 +224,7 @@
 		set_current_state(TASK_INTERRUPTIBLE);
 		add_wait_queue(&wait_q, &wait);
 
-		rc = ctx->mtd->block_isbad(ctx->mtd, erase.addr);
-		if (rc < 0) {
-			printk(KERN_ERR
-			       "apanic: Bad block check "
-			       "failed (%d)\n", rc);
-			goto out;
-		}
-		if (rc) {
+		if (get_bb(erase.addr>>ctx->mtd->erasesize_shift, apanic_bbt)) {
 			printk(KERN_WARNING
 			       "apanic: Skipping erase of bad "
 			       "block @%llx\n", erase.addr);
@@ -187,6 +251,8 @@
 				printk(KERN_INFO
 				       "apanic: Marked a bad block"
 				       " @%llx\n", erase.addr);
+				set_bb(erase.addr>>ctx->mtd->erasesize_shift,
+					apanic_bbt);
 				continue;
 			}
 			goto out;
@@ -237,12 +303,16 @@
 
 	ctx->mtd = mtd;
 
-	if (mtd->block_isbad(mtd, 0)) {
-		printk(KERN_ERR "apanic: Offset 0 bad block. Boourns!\n");
+	alloc_bbt(mtd, apanic_bbt);
+	scan_bbt(mtd, apanic_bbt);
+
+	if (apanic_good_blocks == 0) {
+		printk(KERN_ERR "apanic: no any good blocks?!\n");
 		goto out_err;
 	}
 
-	rc = mtd->read(mtd, 0, mtd->writesize, &len, ctx->bounce);
+	rc = mtd->read(mtd, phy_offset(mtd, 0), mtd->writesize,
+			&len, ctx->bounce);
 	if (rc && rc == -EBADMSG) {
 		printk(KERN_WARNING
 		       "apanic: Bad ECC on block 0 (ignored)\n");
@@ -341,6 +411,12 @@
 		return 0;
 	}
 
+	to = phy_offset(mtd, to);
+	if (to == APANIC_INVALID_OFFSET) {
+		printk(KERN_EMERG "apanic: write to invalid address\n");
+		return 0;
+	}
+
 	if (panic)
 		rc = mtd->panic_write(mtd, to, mtd->writesize, &wlen, buf);
 	else
@@ -386,34 +462,12 @@
 			break;
 		if (rc != mtd->writesize)
 			memset(ctx->bounce + rc, 0, mtd->writesize - rc);
-#if CHECK_BB
-check_badblock:
-		rc = mtd->block_isbad(mtd, off);
-		if (rc < 0) {
-			printk(KERN_ERR
-			       "apanic: Bad block check "
-			       "failed (%d)\n", rc);
-		}
-		if (rc) {
-			printk(KERN_WARNING
-			       "apanic: Skipping over bad "
-			       "block @%x\n", off);
-			off += mtd->erasesize;
-			printk("chk %u %llu\n", off, mtd->size);
-			if (off >= mtd->size) {
-				printk(KERN_EMERG
-				       "apanic: Too many bad blocks!\n");
-				       return -EIO;
-			}
-			goto check_badblock;
-		}
-#endif
 
 		rc2 = apanic_writeflashpage(mtd, off, ctx->bounce);
 		if (rc2 <= 0) {
 			printk(KERN_EMERG
 			       "apanic: Flash write failed (%d)\n", rc2);
-			return rc2;
+			return idx;
 		}
 		if (!last_chunk)
 			idx += rc2;
@@ -442,6 +496,7 @@
 	/* Ensure that cond_resched() won't try to preempt anybody */
 	add_preempt_count(PREEMPT_ACTIVE);
 #endif
+	touch_softlockup_watchdog();
 
 	if (!ctx->mtd)
 		goto out;