Find the bad blocks for each partition.

During kernel init, the BBT is scanned and the bad block is mapped to
corresponding MTD partitions. This information is posted in
/proc/sys/dev/repartition/bbinfo. The information can be used later for
purposes like non-bb check of kernel partition during manufacturing,
diagnostics and statistics.

Change-Id: I3a7e0129b1b48bb611647f08b9baf68c7cf26480
diff --git a/arch/mips/brcmstb/partitionmap.c b/arch/mips/brcmstb/partitionmap.c
index 6dd6374..2136073 100644
--- a/arch/mips/brcmstb/partitionmap.c
+++ b/arch/mips/brcmstb/partitionmap.c
@@ -20,7 +20,7 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/slab.h>
-#include "partitionmap.h"
+#include <mtd/partitionmap.h>
 
 #define CFE_NAME         "cfe"
 #define HNVRAM_NAME      "hnvram"
@@ -131,13 +131,18 @@
 /* By default, use partition map v2. */
 struct mtd_partition *fixed_nor_partition_map = fixed_nor_partition_map_v1;
 int fixed_nor_partition_map_size = ARRAY_SIZE(fixed_nor_partition_map_v1);
+EXPORT_SYMBOL(fixed_nor_partition_map_size);
 
 struct mtd_partition *fixed_nand_partition_map = fixed_nand_partition_map_v2;
 int fixed_nand_partition_map_size = ARRAY_SIZE(fixed_nand_partition_map_v2);
+EXPORT_SYMBOL(fixed_nand_partition_map_size);
 
 static DEFINE_MUTEX(partitionmap_mutex);
 static struct list_head mtd_dev_list = LIST_HEAD_INIT(mtd_dev_list);
 
+static DEFINE_MUTEX(bb_mutex);
+static struct list_head bb_list = LIST_HEAD_INIT(bb_list);
+
 int partitionmap_version = 2;	/* partition map version */
 EXPORT_SYMBOL(partitionmap_version);
 
@@ -146,21 +151,84 @@
 	struct platform_device *pdev;
 };
 
+struct bb_entry {
+	struct list_head list;
+	loff_t offset;
+};
+
+size_t partitionmap_print_bbinfo(char *buffer, size_t size)
+{
+	size_t ret;
+	size_t pos = 0;
+	int i;
+	struct mtd_partition *mtd;
+	struct bb_entry *bb;
+	size_t *bb_map = kzalloc(fixed_nand_partition_map_size*sizeof(size_t),
+				 GFP_KERNEL);
+	if (!bb_map)
+		return 0;
+
+	ret = scnprintf(buffer + pos, size - pos, "partition: badblocks\n");
+	if (!ret) {
+		kfree(bb_map);
+		return 0;
+	}
+
+	pos += ret;
+
+	mutex_lock(&bb_mutex);
+	list_for_each_entry(bb, &bb_list, list) {
+		for (i= 0, mtd = &fixed_nand_partition_map[0];
+		     i < fixed_nand_partition_map_size; ++i, ++mtd) {
+			if ((bb->offset >= mtd->offset) &&
+			    (bb->offset < (mtd->offset + mtd->size))) {
+				++bb_map[i];
+			}
+		}
+	}
+	mutex_unlock(&bb_mutex);
+
+	for (i = 0, mtd = &fixed_nand_partition_map[0];
+	     i < fixed_nand_partition_map_size; ++i, ++mtd) {
+		if (pos < size) {
+			ret = (size_t) scnprintf(
+					buffer + pos, size - pos,
+					"%s: %u\n", mtd->name,
+					bb_map[i]);
+		} else {
+			ret = 0;
+		}
+		if (!ret) {
+			kfree(bb_map);
+			return ret;
+		}
+		pos += ret;
+	}
+
+	if (pos && buffer[pos - 1] == '\n') {
+		buffer[pos - 1] = '\0';
+	}
+
+	kfree(bb_map);
+
+	return pos;
+}
+EXPORT_SYMBOL(partitionmap_print_bbinfo);
+
 size_t partitionmap_print_info(char *buffer, size_t size)
 {
 	size_t ret;
-        size_t pos = 0;
+	size_t pos = 0;
 	struct mtd_dev_entry *mtd;
 
 	ret = scnprintf(buffer + pos, size - pos,
 			"Partition map version: %d\n",
-                        partitionmap_version);
+			partitionmap_version);
 	if (!ret)
 		return 0;
 
 	pos += ret;
 	mutex_lock(&partitionmap_mutex);
-
 	list_for_each_entry(mtd, &mtd_dev_list, list) {
 		if (pos < size) {
 			ret = (size_t)scnprintf(buffer + pos, size - pos,
@@ -175,9 +243,12 @@
 		}
 		pos += ret;
 	}
-
 	mutex_unlock(&partitionmap_mutex);
 
+	if (pos && buffer[pos - 1] == '\n') {
+		buffer[pos - 1] = '\0';
+	}
+
 	return pos;
 }
 EXPORT_SYMBOL(partitionmap_print_info);
@@ -210,12 +281,28 @@
 			return 2;
 	}
 	pr_info("Switched partition from version %d to version %d.\n",
-                partitionmap_version, pver);
-        partitionmap_version = pver;
+		partitionmap_version, pver);
+	partitionmap_version = pver;
 	return 0;
 }
 EXPORT_SYMBOL(switch_partition);
 
+void register_badblock(loff_t offset)
+{
+	struct bb_entry* obj = (struct bb_entry *)
+			kmalloc(sizeof(struct bb_entry), GFP_KERNEL);
+
+	if (!obj)
+		panic("Insufficient memory to allocate MTD device entry\n");
+
+	obj->offset = offset;
+
+	mutex_lock(&bb_mutex);
+	list_add(&obj->list, &bb_list);
+	mutex_unlock(&bb_mutex);
+}
+EXPORT_SYMBOL(register_badblock);
+
 void register_nand(struct platform_device *pdev)
 {
 	struct mtd_dev_entry* obj = (struct mtd_dev_entry *)
@@ -252,7 +339,7 @@
 static int __init partitionver_setup(char *options)
 {
 	int pver;
-        char* endp;
+	char* endp;
 	if (*options == 0)
 		return 0;
 	pver = simple_strtol(options, &endp, 10);
diff --git a/arch/mips/brcmstb/repartition.c b/arch/mips/brcmstb/repartition.c
index cfb9d13..70f5503 100644
--- a/arch/mips/brcmstb/repartition.c
+++ b/arch/mips/brcmstb/repartition.c
@@ -19,15 +19,17 @@
 #include <linux/sysctl.h>
 #include <linux/proc_fs.h>
 #include <linux/init.h>
-#include "partitionmap.h"
+#include <mtd/partitionmap.h>
 #include "repartition.h"
 
 #define PARTITIONMAP_STR_SIZE 512
+#define BADBLOCK_STR_SIZE 512
 
 static const char repartition_proc_name[] = "repartition";
 
 static struct repartition_sysctl_setting {
 	char info[PARTITIONMAP_STR_SIZE];   /* partition map info */
+	char bbinfo[BADBLOCK_STR_SIZE];     /* bad block info */
 	int version;
 	int disable;
 	int disable_min;
@@ -49,15 +51,42 @@
 	return 0;
 }
 
+static int repartition_print_bbinfo(void)
+{
+	int ret = partitionmap_print_bbinfo(setting.bbinfo, sizeof(setting.bbinfo));
+	if (!ret)
+		return 1;
+
+	return 0;
+}
+
 static void reinit_nand(void)
 {
 	flush_nand();
 	init_nand();
 }
 
+static int repartition_sysctl_bbinfo(ctl_table *ctl, int write,
+				     void __user *buffer, size_t *lenp,
+				     loff_t *ppos)
+{
+	if (!*lenp || (*ppos && !write)) {
+		*lenp = 0;
+		return 0;
+	}
+
+	if(repartition_print_bbinfo()) {
+		*lenp = 0;
+		pr_err("insufficient bad block info buffer\n");
+		return -ENOMEM;
+	}
+	proc_dostring(ctl, write, buffer, lenp, ppos);
+	return 0;
+}
+
 static int repartition_sysctl_info(ctl_table *ctl, int write,
-				    void __user *buffer, size_t *lenp,
-				    loff_t *ppos)
+				   void __user *buffer, size_t *lenp,
+				   loff_t *ppos)
 {
 	if (!*lenp || (*ppos && !write)) {
 		*lenp = 0;
@@ -70,7 +99,7 @@
 		return -ENOMEM;
 	}
 	proc_dostring(ctl, write, buffer, lenp, ppos);
-        return 0;
+	return 0;
 }
 
 static int repartition_sysctl_version(ctl_table *ctl, int write,
@@ -109,6 +138,13 @@
 
 static ctl_table repartition_data_table[] = {
 	{
+		.procname       = "bbinfo",
+		.data           = setting.bbinfo,
+		.maxlen         = BADBLOCK_STR_SIZE,
+		.mode           = 0444,
+		.proc_handler   = repartition_sysctl_bbinfo,
+	},
+	{
 		.procname       = "info",
 		.data           = setting.info,
 		.maxlen         = PARTITIONMAP_STR_SIZE,
@@ -170,7 +206,7 @@
 static int __init partitionver_setup(char *options)
 {
 	int pver;
-        char* endp;
+	char* endp;
 	if (*options == 0)
 		return 0;
 	pver = simple_strtol(options, &endp, 10);
@@ -184,7 +220,7 @@
 {
 	if (repartition_sysctl_header)
 		unregister_sysctl_table(repartition_sysctl_header);
-        return 0;
+	return 0;
 }
 module_exit(repartition_exit);
 #endif  /* MODULE */
diff --git a/arch/mips/brcmstb/setup.c b/arch/mips/brcmstb/setup.c
index f3c70ea..ca042a0 100644
--- a/arch/mips/brcmstb/setup.c
+++ b/arch/mips/brcmstb/setup.c
@@ -56,7 +56,7 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/physmap.h>
 #include <linux/mtd/map.h>
-#include "partitionmap.h"
+#include <mtd/partitionmap.h>
 
 /* Default SPI flash chip selects to scan at boot time
    Can be overriden with spics=N kernel boot argument
diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c
index f5be986..c660b94 100644
--- a/drivers/mtd/nand/nand_bbt.c
+++ b/drivers/mtd/nand/nand_bbt.c
@@ -67,6 +67,7 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/vmalloc.h>
+#include <mtd/partitionmap.h>
 
 static int check_pattern_no_oob(uint8_t *buf, struct nand_bbt_descr *td)
 {
@@ -236,6 +237,9 @@
 				 */
 				pr_info("nand_read_bbt: bad block at 0x%012llx\n",
 					 (loff_t)((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
+#ifdef CONFIG_BRUNO
+				register_badblock((loff_t)((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
+#endif
 				/* Factory marked bad or worn out? */
 				if (tmp == 0)
 					this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06);
diff --git a/arch/mips/brcmstb/partitionmap.h b/include/mtd/partitionmap.h
similarity index 89%
rename from arch/mips/brcmstb/partitionmap.h
rename to include/mtd/partitionmap.h
index 4c1ecab..ebc52a9 100644
--- a/arch/mips/brcmstb/partitionmap.h
+++ b/include/mtd/partitionmap.h
@@ -19,9 +19,11 @@
 extern int fixed_nand_partition_map_size;
 extern struct mtd_partition *fixed_nor_partition_map;
 extern struct mtd_partition *fixed_nand_partition_map;
+extern size_t partitionmap_print_bbinfo(char *buffer, size_t size);
 extern size_t partitionmap_print_info(char *buffer, size_t size);
 extern int switch_partition(int pver);
 extern void register_nand(struct platform_device *pdev);
+extern void register_badblock(loff_t offset);
 extern void flush_nand(void);
 
 #endif  /* __PARTITIONMAP_H__ */