xref: /linux/drivers/mtd/sm_ftl.c (revision 745df17906029cc683b8b5ac8bcb08f82860baff)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27d17c02aSMaxim Levitsky /*
37d17c02aSMaxim Levitsky  * Copyright © 2009 - Maxim Levitsky
47d17c02aSMaxim Levitsky  * SmartMedia/xD translation layer
57d17c02aSMaxim Levitsky  */
67d17c02aSMaxim Levitsky 
77d17c02aSMaxim Levitsky #include <linux/kernel.h>
87d17c02aSMaxim Levitsky #include <linux/module.h>
97d17c02aSMaxim Levitsky #include <linux/random.h>
107d17c02aSMaxim Levitsky #include <linux/hdreg.h>
117d17c02aSMaxim Levitsky #include <linux/kthread.h>
127d17c02aSMaxim Levitsky #include <linux/freezer.h>
137d17c02aSMaxim Levitsky #include <linux/sysfs.h>
147d17c02aSMaxim Levitsky #include <linux/bitops.h>
158da552f2SStephen Rothwell #include <linux/slab.h>
16e5acf9c8SMiquel Raynal #include <linux/mtd/nand-ecc-sw-hamming.h>
1793db446aSBoris Brezillon #include "nand/raw/sm_common.h"
187d17c02aSMaxim Levitsky #include "sm_ftl.h"
197d17c02aSMaxim Levitsky 
207d17c02aSMaxim Levitsky 
217d17c02aSMaxim Levitsky 
22582b2ffcSJingoo Han static struct workqueue_struct *cache_flush_workqueue;
237d17c02aSMaxim Levitsky 
247d17c02aSMaxim Levitsky static int cache_timeout = 1000;
25f9fbcdc3SRusty Russell module_param(cache_timeout, int, S_IRUGO);
267d17c02aSMaxim Levitsky MODULE_PARM_DESC(cache_timeout,
277d17c02aSMaxim Levitsky 	"Timeout (in ms) for cache flush (1000 ms default");
287d17c02aSMaxim Levitsky 
297d17c02aSMaxim Levitsky static int debug;
307d17c02aSMaxim Levitsky module_param(debug, int, S_IRUGO | S_IWUSR);
317d17c02aSMaxim Levitsky MODULE_PARM_DESC(debug, "Debug level (0-2)");
327d17c02aSMaxim Levitsky 
337d17c02aSMaxim Levitsky 
3492394b5cSBrian Norris /* ------------------- sysfs attributes ---------------------------------- */
357d17c02aSMaxim Levitsky struct sm_sysfs_attribute {
367d17c02aSMaxim Levitsky 	struct device_attribute dev_attr;
377d17c02aSMaxim Levitsky 	char *data;
387d17c02aSMaxim Levitsky 	int len;
397d17c02aSMaxim Levitsky };
407d17c02aSMaxim Levitsky 
41582b2ffcSJingoo Han static ssize_t sm_attr_show(struct device *dev, struct device_attribute *attr,
427d17c02aSMaxim Levitsky 		     char *buf)
437d17c02aSMaxim Levitsky {
447d17c02aSMaxim Levitsky 	struct sm_sysfs_attribute *sm_attr =
457d17c02aSMaxim Levitsky 		container_of(attr, struct sm_sysfs_attribute, dev_attr);
467d17c02aSMaxim Levitsky 
477d17c02aSMaxim Levitsky 	strncpy(buf, sm_attr->data, sm_attr->len);
487d17c02aSMaxim Levitsky 	return sm_attr->len;
497d17c02aSMaxim Levitsky }
507d17c02aSMaxim Levitsky 
517d17c02aSMaxim Levitsky 
527d17c02aSMaxim Levitsky #define NUM_ATTRIBUTES 1
537d17c02aSMaxim Levitsky #define SM_CIS_VENDOR_OFFSET 0x59
54582b2ffcSJingoo Han static struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl)
557d17c02aSMaxim Levitsky {
567d17c02aSMaxim Levitsky 	struct attribute_group *attr_group;
577d17c02aSMaxim Levitsky 	struct attribute **attributes;
587d17c02aSMaxim Levitsky 	struct sm_sysfs_attribute *vendor_attribute;
59b4c23305SDan Carpenter 	char *vendor;
607d17c02aSMaxim Levitsky 
61b4c23305SDan Carpenter 	vendor = kstrndup(ftl->cis_buffer + SM_CIS_VENDOR_OFFSET,
62b4c23305SDan Carpenter 			  SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET, GFP_KERNEL);
63629286b9SXiaochen Wang 	if (!vendor)
64629286b9SXiaochen Wang 		goto error1;
657d17c02aSMaxim Levitsky 
667d17c02aSMaxim Levitsky 	/* Initialize sysfs attributes */
677d17c02aSMaxim Levitsky 	vendor_attribute =
687d17c02aSMaxim Levitsky 		kzalloc(sizeof(struct sm_sysfs_attribute), GFP_KERNEL);
69629286b9SXiaochen Wang 	if (!vendor_attribute)
70629286b9SXiaochen Wang 		goto error2;
717d17c02aSMaxim Levitsky 
72ca7081d9SMaxim Levitsky 	sysfs_attr_init(&vendor_attribute->dev_attr.attr);
73ca7081d9SMaxim Levitsky 
747d17c02aSMaxim Levitsky 	vendor_attribute->data = vendor;
75b4c23305SDan Carpenter 	vendor_attribute->len = strlen(vendor);
767d17c02aSMaxim Levitsky 	vendor_attribute->dev_attr.attr.name = "vendor";
777d17c02aSMaxim Levitsky 	vendor_attribute->dev_attr.attr.mode = S_IRUGO;
787d17c02aSMaxim Levitsky 	vendor_attribute->dev_attr.show = sm_attr_show;
797d17c02aSMaxim Levitsky 
807d17c02aSMaxim Levitsky 
817d17c02aSMaxim Levitsky 	/* Create array of pointers to the attributes */
826396bb22SKees Cook 	attributes = kcalloc(NUM_ATTRIBUTES + 1, sizeof(struct attribute *),
837d17c02aSMaxim Levitsky 								GFP_KERNEL);
84629286b9SXiaochen Wang 	if (!attributes)
85629286b9SXiaochen Wang 		goto error3;
867d17c02aSMaxim Levitsky 	attributes[0] = &vendor_attribute->dev_attr.attr;
877d17c02aSMaxim Levitsky 
887d17c02aSMaxim Levitsky 	/* Finally create the attribute group */
897d17c02aSMaxim Levitsky 	attr_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL);
90629286b9SXiaochen Wang 	if (!attr_group)
91629286b9SXiaochen Wang 		goto error4;
927d17c02aSMaxim Levitsky 	attr_group->attrs = attributes;
937d17c02aSMaxim Levitsky 	return attr_group;
94629286b9SXiaochen Wang error4:
95629286b9SXiaochen Wang 	kfree(attributes);
96629286b9SXiaochen Wang error3:
97629286b9SXiaochen Wang 	kfree(vendor_attribute);
98629286b9SXiaochen Wang error2:
99629286b9SXiaochen Wang 	kfree(vendor);
100629286b9SXiaochen Wang error1:
101629286b9SXiaochen Wang 	return NULL;
1027d17c02aSMaxim Levitsky }
1037d17c02aSMaxim Levitsky 
104582b2ffcSJingoo Han static void sm_delete_sysfs_attributes(struct sm_ftl *ftl)
1057d17c02aSMaxim Levitsky {
1067d17c02aSMaxim Levitsky 	struct attribute **attributes = ftl->disk_attributes->attrs;
1077d17c02aSMaxim Levitsky 	int i;
1087d17c02aSMaxim Levitsky 
1097d17c02aSMaxim Levitsky 	for (i = 0; attributes[i] ; i++) {
1107d17c02aSMaxim Levitsky 
1117d17c02aSMaxim Levitsky 		struct device_attribute *dev_attr = container_of(attributes[i],
1127d17c02aSMaxim Levitsky 			struct device_attribute, attr);
1137d17c02aSMaxim Levitsky 
1147d17c02aSMaxim Levitsky 		struct sm_sysfs_attribute *sm_attr =
1157d17c02aSMaxim Levitsky 			container_of(dev_attr,
1167d17c02aSMaxim Levitsky 				struct sm_sysfs_attribute, dev_attr);
1177d17c02aSMaxim Levitsky 
1187d17c02aSMaxim Levitsky 		kfree(sm_attr->data);
1197d17c02aSMaxim Levitsky 		kfree(sm_attr);
1207d17c02aSMaxim Levitsky 	}
1217d17c02aSMaxim Levitsky 
1227d17c02aSMaxim Levitsky 	kfree(ftl->disk_attributes->attrs);
1237d17c02aSMaxim Levitsky 	kfree(ftl->disk_attributes);
1247d17c02aSMaxim Levitsky }
1257d17c02aSMaxim Levitsky 
1267d17c02aSMaxim Levitsky 
1277d17c02aSMaxim Levitsky /* ----------------------- oob helpers -------------------------------------- */
1287d17c02aSMaxim Levitsky 
1297d17c02aSMaxim Levitsky static int sm_get_lba(uint8_t *lba)
1307d17c02aSMaxim Levitsky {
1317d17c02aSMaxim Levitsky 	/* check fixed bits */
1327d17c02aSMaxim Levitsky 	if ((lba[0] & 0xF8) != 0x10)
1337d17c02aSMaxim Levitsky 		return -2;
1347d17c02aSMaxim Levitsky 
1357854d3f7SBrian Norris 	/* check parity - endianness doesn't matter */
1367d17c02aSMaxim Levitsky 	if (hweight16(*(uint16_t *)lba) & 1)
1377d17c02aSMaxim Levitsky 		return -2;
1387d17c02aSMaxim Levitsky 
1397d17c02aSMaxim Levitsky 	return (lba[1] >> 1) | ((lba[0] & 0x07) << 7);
1407d17c02aSMaxim Levitsky }
1417d17c02aSMaxim Levitsky 
1427d17c02aSMaxim Levitsky 
1437d17c02aSMaxim Levitsky /*
14492394b5cSBrian Norris  * Read LBA associated with block
1457d17c02aSMaxim Levitsky  * returns -1, if block is erased
1467d17c02aSMaxim Levitsky  * returns -2 if error happens
1477d17c02aSMaxim Levitsky  */
1487d17c02aSMaxim Levitsky static int sm_read_lba(struct sm_oob *oob)
1497d17c02aSMaxim Levitsky {
1507d17c02aSMaxim Levitsky 	static const uint32_t erased_pattern[4] = {
1517d17c02aSMaxim Levitsky 		0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
1527d17c02aSMaxim Levitsky 
1537d17c02aSMaxim Levitsky 	uint16_t lba_test;
1547d17c02aSMaxim Levitsky 	int lba;
1557d17c02aSMaxim Levitsky 
1567d17c02aSMaxim Levitsky 	/* First test for erased block */
1577d17c02aSMaxim Levitsky 	if (!memcmp(oob, erased_pattern, SM_OOB_SIZE))
1587d17c02aSMaxim Levitsky 		return -1;
1597d17c02aSMaxim Levitsky 
1607d17c02aSMaxim Levitsky 	/* Now check is both copies of the LBA differ too much */
1617d17c02aSMaxim Levitsky 	lba_test = *(uint16_t *)oob->lba_copy1 ^ *(uint16_t*)oob->lba_copy2;
1627d17c02aSMaxim Levitsky 	if (lba_test && !is_power_of_2(lba_test))
1637d17c02aSMaxim Levitsky 		return -2;
1647d17c02aSMaxim Levitsky 
1657d17c02aSMaxim Levitsky 	/* And read it */
1667d17c02aSMaxim Levitsky 	lba = sm_get_lba(oob->lba_copy1);
1677d17c02aSMaxim Levitsky 
1687d17c02aSMaxim Levitsky 	if (lba == -2)
1697d17c02aSMaxim Levitsky 		lba = sm_get_lba(oob->lba_copy2);
1707d17c02aSMaxim Levitsky 
1717d17c02aSMaxim Levitsky 	return lba;
1727d17c02aSMaxim Levitsky }
1737d17c02aSMaxim Levitsky 
1747d17c02aSMaxim Levitsky static void sm_write_lba(struct sm_oob *oob, uint16_t lba)
1757d17c02aSMaxim Levitsky {
1767d17c02aSMaxim Levitsky 	uint8_t tmp[2];
1777d17c02aSMaxim Levitsky 
1787d17c02aSMaxim Levitsky 	WARN_ON(lba >= 1000);
1797d17c02aSMaxim Levitsky 
1807d17c02aSMaxim Levitsky 	tmp[0] = 0x10 | ((lba >> 7) & 0x07);
1817d17c02aSMaxim Levitsky 	tmp[1] = (lba << 1) & 0xFF;
1827d17c02aSMaxim Levitsky 
1837d17c02aSMaxim Levitsky 	if (hweight16(*(uint16_t *)tmp) & 0x01)
1847d17c02aSMaxim Levitsky 		tmp[1] |= 1;
1857d17c02aSMaxim Levitsky 
1867d17c02aSMaxim Levitsky 	oob->lba_copy1[0] = oob->lba_copy2[0] = tmp[0];
1877d17c02aSMaxim Levitsky 	oob->lba_copy1[1] = oob->lba_copy2[1] = tmp[1];
1887d17c02aSMaxim Levitsky }
1897d17c02aSMaxim Levitsky 
1907d17c02aSMaxim Levitsky 
1917d17c02aSMaxim Levitsky /* Make offset from parts */
1927d17c02aSMaxim Levitsky static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset)
1937d17c02aSMaxim Levitsky {
1947d17c02aSMaxim Levitsky 	WARN_ON(boffset & (SM_SECTOR_SIZE - 1));
1957d17c02aSMaxim Levitsky 	WARN_ON(zone < 0 || zone >= ftl->zone_count);
1967d17c02aSMaxim Levitsky 	WARN_ON(block >= ftl->zone_size);
1977d17c02aSMaxim Levitsky 	WARN_ON(boffset >= ftl->block_size);
1987d17c02aSMaxim Levitsky 
1997d17c02aSMaxim Levitsky 	if (block == -1)
2007d17c02aSMaxim Levitsky 		return -1;
2017d17c02aSMaxim Levitsky 
2027d17c02aSMaxim Levitsky 	return (zone * SM_MAX_ZONE_SIZE + block) * ftl->block_size + boffset;
2037d17c02aSMaxim Levitsky }
2047d17c02aSMaxim Levitsky 
2057d17c02aSMaxim Levitsky /* Breaks offset into parts */
2062b2462d5SNicolas Pitre static void sm_break_offset(struct sm_ftl *ftl, loff_t loffset,
2077d17c02aSMaxim Levitsky 			    int *zone, int *block, int *boffset)
2087d17c02aSMaxim Levitsky {
2092b2462d5SNicolas Pitre 	u64 offset = loffset;
2107d17c02aSMaxim Levitsky 	*boffset = do_div(offset, ftl->block_size);
2117d17c02aSMaxim Levitsky 	*block = do_div(offset, ftl->max_lba);
2127d17c02aSMaxim Levitsky 	*zone = offset >= ftl->zone_count ? -1 : offset;
2137d17c02aSMaxim Levitsky }
2147d17c02aSMaxim Levitsky 
2157d17c02aSMaxim Levitsky /* ---------------------- low level IO ------------------------------------- */
2167d17c02aSMaxim Levitsky 
2177d17c02aSMaxim Levitsky static int sm_correct_sector(uint8_t *buffer, struct sm_oob *oob)
2187d17c02aSMaxim Levitsky {
21990ccf0a0SMiquel Raynal 	bool sm_order = IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING_SMC);
2207d17c02aSMaxim Levitsky 	uint8_t ecc[3];
2217d17c02aSMaxim Levitsky 
22290ccf0a0SMiquel Raynal 	ecc_sw_hamming_calculate(buffer, SM_SMALL_PAGE, ecc, sm_order);
22390ccf0a0SMiquel Raynal 	if (ecc_sw_hamming_correct(buffer, ecc, oob->ecc1, SM_SMALL_PAGE,
22490ccf0a0SMiquel Raynal 				   sm_order) < 0)
2257d17c02aSMaxim Levitsky 		return -EIO;
2267d17c02aSMaxim Levitsky 
2277d17c02aSMaxim Levitsky 	buffer += SM_SMALL_PAGE;
2287d17c02aSMaxim Levitsky 
22990ccf0a0SMiquel Raynal 	ecc_sw_hamming_calculate(buffer, SM_SMALL_PAGE, ecc, sm_order);
23090ccf0a0SMiquel Raynal 	if (ecc_sw_hamming_correct(buffer, ecc, oob->ecc2, SM_SMALL_PAGE,
23190ccf0a0SMiquel Raynal 				   sm_order) < 0)
2327d17c02aSMaxim Levitsky 		return -EIO;
2337d17c02aSMaxim Levitsky 	return 0;
2347d17c02aSMaxim Levitsky }
2357d17c02aSMaxim Levitsky 
2367d17c02aSMaxim Levitsky /* Reads a sector + oob*/
2377d17c02aSMaxim Levitsky static int sm_read_sector(struct sm_ftl *ftl,
2387d17c02aSMaxim Levitsky 			  int zone, int block, int boffset,
2397d17c02aSMaxim Levitsky 			  uint8_t *buffer, struct sm_oob *oob)
2407d17c02aSMaxim Levitsky {
2417d17c02aSMaxim Levitsky 	struct mtd_info *mtd = ftl->trans->mtd;
242*745df179SMichał Kępień 	struct mtd_oob_ops ops = { };
2437d17c02aSMaxim Levitsky 	struct sm_oob tmp_oob;
244133fa8c7SMaxim Levitsky 	int ret = -EIO;
2457d17c02aSMaxim Levitsky 	int try = 0;
2467d17c02aSMaxim Levitsky 
2477d17c02aSMaxim Levitsky 	/* FTL can contain -1 entries that are by default filled with bits */
2487d17c02aSMaxim Levitsky 	if (block == -1) {
249de08b5acSArnd Bergmann 		if (buffer)
2507d17c02aSMaxim Levitsky 			memset(buffer, 0xFF, SM_SECTOR_SIZE);
2517d17c02aSMaxim Levitsky 		return 0;
2527d17c02aSMaxim Levitsky 	}
2537d17c02aSMaxim Levitsky 
25492394b5cSBrian Norris 	/* User might not need the oob, but we do for data verification */
2557d17c02aSMaxim Levitsky 	if (!oob)
2567d17c02aSMaxim Levitsky 		oob = &tmp_oob;
2577d17c02aSMaxim Levitsky 
2580612b9ddSBrian Norris 	ops.mode = ftl->smallpagenand ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB;
2597d17c02aSMaxim Levitsky 	ops.ooboffs = 0;
2607d17c02aSMaxim Levitsky 	ops.ooblen = SM_OOB_SIZE;
2617d17c02aSMaxim Levitsky 	ops.oobbuf = (void *)oob;
2627d17c02aSMaxim Levitsky 	ops.len = SM_SECTOR_SIZE;
2637d17c02aSMaxim Levitsky 	ops.datbuf = buffer;
2647d17c02aSMaxim Levitsky 
2657d17c02aSMaxim Levitsky again:
2667d17c02aSMaxim Levitsky 	if (try++) {
2677d17c02aSMaxim Levitsky 		/* Avoid infinite recursion on CIS reads, sm_recheck_media
268cc9d663aSShubhankar Kuranagatti 		 * won't help anyway
269cc9d663aSShubhankar Kuranagatti 		 */
2707d17c02aSMaxim Levitsky 		if (zone == 0 && block == ftl->cis_block && boffset ==
2717d17c02aSMaxim Levitsky 			ftl->cis_boffset)
2727d17c02aSMaxim Levitsky 			return ret;
2737d17c02aSMaxim Levitsky 
2747d17c02aSMaxim Levitsky 		/* Test if media is stable */
2757d17c02aSMaxim Levitsky 		if (try == 3 || sm_recheck_media(ftl))
2767d17c02aSMaxim Levitsky 			return ret;
2777d17c02aSMaxim Levitsky 	}
2787d17c02aSMaxim Levitsky 
27992394b5cSBrian Norris 	/* Unfortunately, oob read will _always_ succeed,
280cc9d663aSShubhankar Kuranagatti 	 * despite card removal.....
281cc9d663aSShubhankar Kuranagatti 	 */
282fd2819bbSArtem Bityutskiy 	ret = mtd_read_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops);
2837d17c02aSMaxim Levitsky 
2847d17c02aSMaxim Levitsky 	/* Test for unknown errors */
285d57f4054SBrian Norris 	if (ret != 0 && !mtd_is_bitflip_or_eccerr(ret)) {
2867d17c02aSMaxim Levitsky 		dbg("read of block %d at zone %d, failed due to error (%d)",
2877d17c02aSMaxim Levitsky 			block, zone, ret);
2887d17c02aSMaxim Levitsky 		goto again;
2897d17c02aSMaxim Levitsky 	}
2907d17c02aSMaxim Levitsky 
2917d17c02aSMaxim Levitsky 	/* Do a basic test on the oob, to guard against returned garbage */
2927d17c02aSMaxim Levitsky 	if (oob->reserved != 0xFFFFFFFF && !is_power_of_2(~oob->reserved))
2937d17c02aSMaxim Levitsky 		goto again;
2947d17c02aSMaxim Levitsky 
2957d17c02aSMaxim Levitsky 	/* This should never happen, unless there is a bug in the mtd driver */
2967d17c02aSMaxim Levitsky 	WARN_ON(ops.oobretlen != SM_OOB_SIZE);
2977d17c02aSMaxim Levitsky 	WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE);
2987d17c02aSMaxim Levitsky 
2997d17c02aSMaxim Levitsky 	if (!buffer)
3007d17c02aSMaxim Levitsky 		return 0;
3017d17c02aSMaxim Levitsky 
3027d17c02aSMaxim Levitsky 	/* Test if sector marked as bad */
3037d17c02aSMaxim Levitsky 	if (!sm_sector_valid(oob)) {
3047d17c02aSMaxim Levitsky 		dbg("read of block %d at zone %d, failed because it is marked"
3057d17c02aSMaxim Levitsky 			" as bad" , block, zone);
3067d17c02aSMaxim Levitsky 		goto again;
3077d17c02aSMaxim Levitsky 	}
3087d17c02aSMaxim Levitsky 
3097d17c02aSMaxim Levitsky 	/* Test ECC*/
310d57f4054SBrian Norris 	if (mtd_is_eccerr(ret) ||
3117d17c02aSMaxim Levitsky 		(ftl->smallpagenand && sm_correct_sector(buffer, oob))) {
3127d17c02aSMaxim Levitsky 
3137d17c02aSMaxim Levitsky 		dbg("read of block %d at zone %d, failed due to ECC error",
3147d17c02aSMaxim Levitsky 			block, zone);
3157d17c02aSMaxim Levitsky 		goto again;
3167d17c02aSMaxim Levitsky 	}
3177d17c02aSMaxim Levitsky 
3187d17c02aSMaxim Levitsky 	return 0;
3197d17c02aSMaxim Levitsky }
3207d17c02aSMaxim Levitsky 
3217d17c02aSMaxim Levitsky /* Writes a sector to media */
3227d17c02aSMaxim Levitsky static int sm_write_sector(struct sm_ftl *ftl,
3237d17c02aSMaxim Levitsky 			   int zone, int block, int boffset,
3247d17c02aSMaxim Levitsky 			   uint8_t *buffer, struct sm_oob *oob)
3257d17c02aSMaxim Levitsky {
326*745df179SMichał Kępień 	struct mtd_oob_ops ops = { };
3277d17c02aSMaxim Levitsky 	struct mtd_info *mtd = ftl->trans->mtd;
3287d17c02aSMaxim Levitsky 	int ret;
3297d17c02aSMaxim Levitsky 
3307d17c02aSMaxim Levitsky 	BUG_ON(ftl->readonly);
3317d17c02aSMaxim Levitsky 
3327d17c02aSMaxim Levitsky 	if (zone == 0 && (block == ftl->cis_block || block == 0)) {
3337d17c02aSMaxim Levitsky 		dbg("attempted to write the CIS!");
3347d17c02aSMaxim Levitsky 		return -EIO;
3357d17c02aSMaxim Levitsky 	}
3367d17c02aSMaxim Levitsky 
3377d17c02aSMaxim Levitsky 	if (ftl->unstable)
3387d17c02aSMaxim Levitsky 		return -EIO;
3397d17c02aSMaxim Levitsky 
3400612b9ddSBrian Norris 	ops.mode = ftl->smallpagenand ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB;
3417d17c02aSMaxim Levitsky 	ops.len = SM_SECTOR_SIZE;
3427d17c02aSMaxim Levitsky 	ops.datbuf = buffer;
3437d17c02aSMaxim Levitsky 	ops.ooboffs = 0;
3447d17c02aSMaxim Levitsky 	ops.ooblen = SM_OOB_SIZE;
3457d17c02aSMaxim Levitsky 	ops.oobbuf = (void *)oob;
3467d17c02aSMaxim Levitsky 
347a2cc5ba0SArtem Bityutskiy 	ret = mtd_write_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops);
3487d17c02aSMaxim Levitsky 
3497d17c02aSMaxim Levitsky 	/* Now we assume that hardware will catch write bitflip errors */
3507d17c02aSMaxim Levitsky 
3517d17c02aSMaxim Levitsky 	if (ret) {
3527d17c02aSMaxim Levitsky 		dbg("write to block %d at zone %d, failed with error %d",
3537d17c02aSMaxim Levitsky 			block, zone, ret);
3547d17c02aSMaxim Levitsky 
3557d17c02aSMaxim Levitsky 		sm_recheck_media(ftl);
3567d17c02aSMaxim Levitsky 		return ret;
3577d17c02aSMaxim Levitsky 	}
3587d17c02aSMaxim Levitsky 
3597d17c02aSMaxim Levitsky 	/* This should never happen, unless there is a bug in the driver */
3607d17c02aSMaxim Levitsky 	WARN_ON(ops.oobretlen != SM_OOB_SIZE);
3617d17c02aSMaxim Levitsky 	WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE);
3627d17c02aSMaxim Levitsky 
3637d17c02aSMaxim Levitsky 	return 0;
3647d17c02aSMaxim Levitsky }
3657d17c02aSMaxim Levitsky 
3667d17c02aSMaxim Levitsky /* ------------------------ block IO ------------------------------------- */
3677d17c02aSMaxim Levitsky 
3687d17c02aSMaxim Levitsky /* Write a block using data and lba, and invalid sector bitmap */
3697d17c02aSMaxim Levitsky static int sm_write_block(struct sm_ftl *ftl, uint8_t *buf,
3707d17c02aSMaxim Levitsky 			  int zone, int block, int lba,
3717d17c02aSMaxim Levitsky 			  unsigned long invalid_bitmap)
3727d17c02aSMaxim Levitsky {
37390ccf0a0SMiquel Raynal 	bool sm_order = IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING_SMC);
3747d17c02aSMaxim Levitsky 	struct sm_oob oob;
3757d17c02aSMaxim Levitsky 	int boffset;
3767d17c02aSMaxim Levitsky 	int retry = 0;
3777d17c02aSMaxim Levitsky 
3787d17c02aSMaxim Levitsky 	/* Initialize the oob with requested values */
3797d17c02aSMaxim Levitsky 	memset(&oob, 0xFF, SM_OOB_SIZE);
3807d17c02aSMaxim Levitsky 	sm_write_lba(&oob, lba);
3817d17c02aSMaxim Levitsky restart:
3827d17c02aSMaxim Levitsky 	if (ftl->unstable)
3837d17c02aSMaxim Levitsky 		return -EIO;
3847d17c02aSMaxim Levitsky 
3857d17c02aSMaxim Levitsky 	for (boffset = 0; boffset < ftl->block_size;
3867d17c02aSMaxim Levitsky 				boffset += SM_SECTOR_SIZE) {
3877d17c02aSMaxim Levitsky 
3887d17c02aSMaxim Levitsky 		oob.data_status = 0xFF;
3897d17c02aSMaxim Levitsky 
3907d17c02aSMaxim Levitsky 		if (test_bit(boffset / SM_SECTOR_SIZE, &invalid_bitmap)) {
3917d17c02aSMaxim Levitsky 
3927d17c02aSMaxim Levitsky 			sm_printk("sector %d of block at LBA %d of zone %d"
393c19ca6cbSMasanari Iida 				" couldn't be read, marking it as invalid",
3947d17c02aSMaxim Levitsky 				boffset / SM_SECTOR_SIZE, lba, zone);
3957d17c02aSMaxim Levitsky 
3967d17c02aSMaxim Levitsky 			oob.data_status = 0;
3977d17c02aSMaxim Levitsky 		}
3987d17c02aSMaxim Levitsky 
3997d17c02aSMaxim Levitsky 		if (ftl->smallpagenand) {
40090ccf0a0SMiquel Raynal 			ecc_sw_hamming_calculate(buf + boffset,
40190ccf0a0SMiquel Raynal 						 SM_SMALL_PAGE, oob.ecc1,
40290ccf0a0SMiquel Raynal 						 sm_order);
4037d17c02aSMaxim Levitsky 
40490ccf0a0SMiquel Raynal 			ecc_sw_hamming_calculate(buf + boffset + SM_SMALL_PAGE,
405309600c1SBoris Brezillon 						 SM_SMALL_PAGE, oob.ecc2,
40690ccf0a0SMiquel Raynal 						 sm_order);
4077d17c02aSMaxim Levitsky 		}
4087d17c02aSMaxim Levitsky 		if (!sm_write_sector(ftl, zone, block, boffset,
4097d17c02aSMaxim Levitsky 							buf + boffset, &oob))
4107d17c02aSMaxim Levitsky 			continue;
4117d17c02aSMaxim Levitsky 
4127d17c02aSMaxim Levitsky 		if (!retry) {
4137d17c02aSMaxim Levitsky 
4147d17c02aSMaxim Levitsky 			/* If write fails. try to erase the block */
4157d17c02aSMaxim Levitsky 			/* This is safe, because we never write in blocks
416cc9d663aSShubhankar Kuranagatti 			 * that contain valuable data.
417cc9d663aSShubhankar Kuranagatti 			 * This is intended to repair block that are marked
418cc9d663aSShubhankar Kuranagatti 			 * as erased, but that isn't fully erased
419cc9d663aSShubhankar Kuranagatti 			 */
4207d17c02aSMaxim Levitsky 
4217d17c02aSMaxim Levitsky 			if (sm_erase_block(ftl, zone, block, 0))
4227d17c02aSMaxim Levitsky 				return -EIO;
4237d17c02aSMaxim Levitsky 
4247d17c02aSMaxim Levitsky 			retry = 1;
4257d17c02aSMaxim Levitsky 			goto restart;
4267d17c02aSMaxim Levitsky 		} else {
4277d17c02aSMaxim Levitsky 			sm_mark_block_bad(ftl, zone, block);
4287d17c02aSMaxim Levitsky 			return -EIO;
4297d17c02aSMaxim Levitsky 		}
4307d17c02aSMaxim Levitsky 	}
4317d17c02aSMaxim Levitsky 	return 0;
4327d17c02aSMaxim Levitsky }
4337d17c02aSMaxim Levitsky 
4347d17c02aSMaxim Levitsky 
4357d17c02aSMaxim Levitsky /* Mark whole block at offset 'offs' as bad. */
4367d17c02aSMaxim Levitsky static void sm_mark_block_bad(struct sm_ftl *ftl, int zone, int block)
4377d17c02aSMaxim Levitsky {
4387d17c02aSMaxim Levitsky 	struct sm_oob oob;
4397d17c02aSMaxim Levitsky 	int boffset;
4407d17c02aSMaxim Levitsky 
4417d17c02aSMaxim Levitsky 	memset(&oob, 0xFF, SM_OOB_SIZE);
4427d17c02aSMaxim Levitsky 	oob.block_status = 0xF0;
4437d17c02aSMaxim Levitsky 
4447d17c02aSMaxim Levitsky 	if (ftl->unstable)
4457d17c02aSMaxim Levitsky 		return;
4467d17c02aSMaxim Levitsky 
4477d17c02aSMaxim Levitsky 	if (sm_recheck_media(ftl))
4487d17c02aSMaxim Levitsky 		return;
4497d17c02aSMaxim Levitsky 
4507d17c02aSMaxim Levitsky 	sm_printk("marking block %d of zone %d as bad", block, zone);
4517d17c02aSMaxim Levitsky 
4527d17c02aSMaxim Levitsky 	/* We aren't checking the return value, because we don't care */
4537d17c02aSMaxim Levitsky 	/* This also fails on fake xD cards, but I guess these won't expose
454cc9d663aSShubhankar Kuranagatti 	 * any bad blocks till fail completely
455cc9d663aSShubhankar Kuranagatti 	 */
4567d17c02aSMaxim Levitsky 	for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
4577d17c02aSMaxim Levitsky 		sm_write_sector(ftl, zone, block, boffset, NULL, &oob);
4587d17c02aSMaxim Levitsky }
4597d17c02aSMaxim Levitsky 
4607d17c02aSMaxim Levitsky /*
4617d17c02aSMaxim Levitsky  * Erase a block within a zone
46292394b5cSBrian Norris  * If erase succeeds, it updates free block fifo, otherwise marks block as bad
4637d17c02aSMaxim Levitsky  */
4647d17c02aSMaxim Levitsky static int sm_erase_block(struct sm_ftl *ftl, int zone_num, uint16_t block,
4657d17c02aSMaxim Levitsky 			  int put_free)
4667d17c02aSMaxim Levitsky {
4677d17c02aSMaxim Levitsky 	struct ftl_zone *zone = &ftl->zones[zone_num];
4687d17c02aSMaxim Levitsky 	struct mtd_info *mtd = ftl->trans->mtd;
4697d17c02aSMaxim Levitsky 	struct erase_info erase;
4707d17c02aSMaxim Levitsky 
4717d17c02aSMaxim Levitsky 	erase.addr = sm_mkoffset(ftl, zone_num, block, 0);
4727d17c02aSMaxim Levitsky 	erase.len = ftl->block_size;
4737d17c02aSMaxim Levitsky 
4747d17c02aSMaxim Levitsky 	if (ftl->unstable)
4757d17c02aSMaxim Levitsky 		return -EIO;
4767d17c02aSMaxim Levitsky 
4777d17c02aSMaxim Levitsky 	BUG_ON(ftl->readonly);
4787d17c02aSMaxim Levitsky 
4797d17c02aSMaxim Levitsky 	if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
4807d17c02aSMaxim Levitsky 		sm_printk("attempted to erase the CIS!");
4817d17c02aSMaxim Levitsky 		return -EIO;
4827d17c02aSMaxim Levitsky 	}
4837d17c02aSMaxim Levitsky 
4847e1f0dc0SArtem Bityutskiy 	if (mtd_erase(mtd, &erase)) {
4857d17c02aSMaxim Levitsky 		sm_printk("erase of block %d in zone %d failed",
4867d17c02aSMaxim Levitsky 							block, zone_num);
4877d17c02aSMaxim Levitsky 		goto error;
4887d17c02aSMaxim Levitsky 	}
4897d17c02aSMaxim Levitsky 
4907d17c02aSMaxim Levitsky 	if (put_free)
4917d17c02aSMaxim Levitsky 		kfifo_in(&zone->free_sectors,
4927d17c02aSMaxim Levitsky 			(const unsigned char *)&block, sizeof(block));
4937d17c02aSMaxim Levitsky 
4947d17c02aSMaxim Levitsky 	return 0;
4957d17c02aSMaxim Levitsky error:
4967d17c02aSMaxim Levitsky 	sm_mark_block_bad(ftl, zone_num, block);
4977d17c02aSMaxim Levitsky 	return -EIO;
4987d17c02aSMaxim Levitsky }
4997d17c02aSMaxim Levitsky 
50092394b5cSBrian Norris /* Thoroughly test that block is valid. */
5017d17c02aSMaxim Levitsky static int sm_check_block(struct sm_ftl *ftl, int zone, int block)
5027d17c02aSMaxim Levitsky {
5037d17c02aSMaxim Levitsky 	int boffset;
5047d17c02aSMaxim Levitsky 	struct sm_oob oob;
5057d17c02aSMaxim Levitsky 	int lbas[] = { -3, 0, 0, 0 };
5067d17c02aSMaxim Levitsky 	int i = 0;
5077d17c02aSMaxim Levitsky 	int test_lba;
5087d17c02aSMaxim Levitsky 
5097d17c02aSMaxim Levitsky 
5107d17c02aSMaxim Levitsky 	/* First just check that block doesn't look fishy */
5117d17c02aSMaxim Levitsky 	/* Only blocks that are valid or are sliced in two parts, are
512cc9d663aSShubhankar Kuranagatti 	 * accepted
513cc9d663aSShubhankar Kuranagatti 	 */
5147d17c02aSMaxim Levitsky 	for (boffset = 0; boffset < ftl->block_size;
5157d17c02aSMaxim Levitsky 					boffset += SM_SECTOR_SIZE) {
5167d17c02aSMaxim Levitsky 
51792394b5cSBrian Norris 		/* This shouldn't happen anyway */
5187d17c02aSMaxim Levitsky 		if (sm_read_sector(ftl, zone, block, boffset, NULL, &oob))
5197d17c02aSMaxim Levitsky 			return -2;
5207d17c02aSMaxim Levitsky 
5217d17c02aSMaxim Levitsky 		test_lba = sm_read_lba(&oob);
5227d17c02aSMaxim Levitsky 
5237d17c02aSMaxim Levitsky 		if (lbas[i] != test_lba)
5247d17c02aSMaxim Levitsky 			lbas[++i] = test_lba;
5257d17c02aSMaxim Levitsky 
5267d17c02aSMaxim Levitsky 		/* If we found three different LBAs, something is fishy */
5277d17c02aSMaxim Levitsky 		if (i == 3)
5287d17c02aSMaxim Levitsky 			return -EIO;
5297d17c02aSMaxim Levitsky 	}
5307d17c02aSMaxim Levitsky 
53125985edcSLucas De Marchi 	/* If the block is sliced (partially erased usually) erase it */
5327d17c02aSMaxim Levitsky 	if (i == 2) {
5337d17c02aSMaxim Levitsky 		sm_erase_block(ftl, zone, block, 1);
5347d17c02aSMaxim Levitsky 		return 1;
5357d17c02aSMaxim Levitsky 	}
5367d17c02aSMaxim Levitsky 
5377d17c02aSMaxim Levitsky 	return 0;
5387d17c02aSMaxim Levitsky }
5397d17c02aSMaxim Levitsky 
5407d17c02aSMaxim Levitsky /* ----------------- media scanning --------------------------------- */
5417d17c02aSMaxim Levitsky static const struct chs_entry chs_table[] = {
5427d17c02aSMaxim Levitsky 	{ 1,    125,  4,  4  },
5437d17c02aSMaxim Levitsky 	{ 2,    125,  4,  8  },
5447d17c02aSMaxim Levitsky 	{ 4,    250,  4,  8  },
5457d17c02aSMaxim Levitsky 	{ 8,    250,  4,  16 },
5467d17c02aSMaxim Levitsky 	{ 16,   500,  4,  16 },
5477d17c02aSMaxim Levitsky 	{ 32,   500,  8,  16 },
5487d17c02aSMaxim Levitsky 	{ 64,   500,  8,  32 },
5497d17c02aSMaxim Levitsky 	{ 128,  500,  16, 32 },
5507d17c02aSMaxim Levitsky 	{ 256,  1000, 16, 32 },
5517d17c02aSMaxim Levitsky 	{ 512,  1015, 32, 63 },
5527d17c02aSMaxim Levitsky 	{ 1024, 985,  33, 63 },
5537d17c02aSMaxim Levitsky 	{ 2048, 985,  33, 63 },
5547d17c02aSMaxim Levitsky 	{ 0 },
5557d17c02aSMaxim Levitsky };
5567d17c02aSMaxim Levitsky 
5577d17c02aSMaxim Levitsky 
5587d17c02aSMaxim Levitsky static const uint8_t cis_signature[] = {
5597d17c02aSMaxim Levitsky 	0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
5607d17c02aSMaxim Levitsky };
5617d17c02aSMaxim Levitsky /* Find out media parameters.
562cc9d663aSShubhankar Kuranagatti  * This ideally has to be based on nand id, but for now device size is enough
563cc9d663aSShubhankar Kuranagatti  */
564582b2ffcSJingoo Han static int sm_get_media_info(struct sm_ftl *ftl, struct mtd_info *mtd)
5657d17c02aSMaxim Levitsky {
5667d17c02aSMaxim Levitsky 	int i;
5677d17c02aSMaxim Levitsky 	int size_in_megs = mtd->size / (1024 * 1024);
5687d17c02aSMaxim Levitsky 
5697d17c02aSMaxim Levitsky 	ftl->readonly = mtd->type == MTD_ROM;
5707d17c02aSMaxim Levitsky 
5717d17c02aSMaxim Levitsky 	/* Manual settings for very old devices */
5727d17c02aSMaxim Levitsky 	ftl->zone_count = 1;
5737d17c02aSMaxim Levitsky 	ftl->smallpagenand = 0;
5747d17c02aSMaxim Levitsky 
5757d17c02aSMaxim Levitsky 	switch (size_in_megs) {
5767d17c02aSMaxim Levitsky 	case 1:
5777d17c02aSMaxim Levitsky 		/* 1 MiB flash/rom SmartMedia card (256 byte pages)*/
5787d17c02aSMaxim Levitsky 		ftl->zone_size = 256;
5797d17c02aSMaxim Levitsky 		ftl->max_lba = 250;
5807d17c02aSMaxim Levitsky 		ftl->block_size = 8 * SM_SECTOR_SIZE;
5817d17c02aSMaxim Levitsky 		ftl->smallpagenand = 1;
5827d17c02aSMaxim Levitsky 
5837d17c02aSMaxim Levitsky 		break;
5847d17c02aSMaxim Levitsky 	case 2:
5857d17c02aSMaxim Levitsky 		/* 2 MiB flash SmartMedia (256 byte pages)*/
5867d17c02aSMaxim Levitsky 		if (mtd->writesize == SM_SMALL_PAGE) {
5877d17c02aSMaxim Levitsky 			ftl->zone_size = 512;
5887d17c02aSMaxim Levitsky 			ftl->max_lba = 500;
5897d17c02aSMaxim Levitsky 			ftl->block_size = 8 * SM_SECTOR_SIZE;
5907d17c02aSMaxim Levitsky 			ftl->smallpagenand = 1;
5917d17c02aSMaxim Levitsky 		/* 2 MiB rom SmartMedia */
5927d17c02aSMaxim Levitsky 		} else {
5937d17c02aSMaxim Levitsky 
5947d17c02aSMaxim Levitsky 			if (!ftl->readonly)
5957d17c02aSMaxim Levitsky 				return -ENODEV;
5967d17c02aSMaxim Levitsky 
5977d17c02aSMaxim Levitsky 			ftl->zone_size = 256;
5987d17c02aSMaxim Levitsky 			ftl->max_lba = 250;
5997d17c02aSMaxim Levitsky 			ftl->block_size = 16 * SM_SECTOR_SIZE;
6007d17c02aSMaxim Levitsky 		}
6017d17c02aSMaxim Levitsky 		break;
6027d17c02aSMaxim Levitsky 	case 4:
6037d17c02aSMaxim Levitsky 		/* 4 MiB flash/rom SmartMedia device */
6047d17c02aSMaxim Levitsky 		ftl->zone_size = 512;
6057d17c02aSMaxim Levitsky 		ftl->max_lba = 500;
6067d17c02aSMaxim Levitsky 		ftl->block_size = 16 * SM_SECTOR_SIZE;
6077d17c02aSMaxim Levitsky 		break;
6087d17c02aSMaxim Levitsky 	case 8:
6097d17c02aSMaxim Levitsky 		/* 8 MiB flash/rom SmartMedia device */
6107d17c02aSMaxim Levitsky 		ftl->zone_size = 1024;
6117d17c02aSMaxim Levitsky 		ftl->max_lba = 1000;
6127d17c02aSMaxim Levitsky 		ftl->block_size = 16 * SM_SECTOR_SIZE;
6137d17c02aSMaxim Levitsky 	}
6147d17c02aSMaxim Levitsky 
6157d17c02aSMaxim Levitsky 	/* Minimum xD size is 16MiB. Also, all xD cards have standard zone
616cc9d663aSShubhankar Kuranagatti 	 * sizes. SmartMedia cards exist up to 128 MiB and have same layout
617cc9d663aSShubhankar Kuranagatti 	 */
6187d17c02aSMaxim Levitsky 	if (size_in_megs >= 16) {
6197d17c02aSMaxim Levitsky 		ftl->zone_count = size_in_megs / 16;
6207d17c02aSMaxim Levitsky 		ftl->zone_size = 1024;
6217d17c02aSMaxim Levitsky 		ftl->max_lba = 1000;
6227d17c02aSMaxim Levitsky 		ftl->block_size = 32 * SM_SECTOR_SIZE;
6237d17c02aSMaxim Levitsky 	}
6247d17c02aSMaxim Levitsky 
6257d17c02aSMaxim Levitsky 	/* Test for proper write,erase and oob sizes */
6267d17c02aSMaxim Levitsky 	if (mtd->erasesize > ftl->block_size)
6277d17c02aSMaxim Levitsky 		return -ENODEV;
6287d17c02aSMaxim Levitsky 
6297d17c02aSMaxim Levitsky 	if (mtd->writesize > SM_SECTOR_SIZE)
6307d17c02aSMaxim Levitsky 		return -ENODEV;
6317d17c02aSMaxim Levitsky 
6327d17c02aSMaxim Levitsky 	if (ftl->smallpagenand && mtd->oobsize < SM_SMALL_OOB_SIZE)
6337d17c02aSMaxim Levitsky 		return -ENODEV;
6347d17c02aSMaxim Levitsky 
6357d17c02aSMaxim Levitsky 	if (!ftl->smallpagenand && mtd->oobsize < SM_OOB_SIZE)
6367d17c02aSMaxim Levitsky 		return -ENODEV;
6377d17c02aSMaxim Levitsky 
638fc002e3cSArtem Bityutskiy 	/* We use OOB */
639fc002e3cSArtem Bityutskiy 	if (!mtd_has_oob(mtd))
6407d17c02aSMaxim Levitsky 		return -ENODEV;
6417d17c02aSMaxim Levitsky 
6427d17c02aSMaxim Levitsky 	/* Find geometry information */
6437d17c02aSMaxim Levitsky 	for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++) {
6447d17c02aSMaxim Levitsky 		if (chs_table[i].size == size_in_megs) {
6457d17c02aSMaxim Levitsky 			ftl->cylinders = chs_table[i].cyl;
6467d17c02aSMaxim Levitsky 			ftl->heads = chs_table[i].head;
6477d17c02aSMaxim Levitsky 			ftl->sectors = chs_table[i].sec;
6487d17c02aSMaxim Levitsky 			return 0;
6497d17c02aSMaxim Levitsky 		}
6507d17c02aSMaxim Levitsky 	}
6517d17c02aSMaxim Levitsky 
6527d17c02aSMaxim Levitsky 	sm_printk("media has unknown size : %dMiB", size_in_megs);
6537d17c02aSMaxim Levitsky 	ftl->cylinders = 985;
6547d17c02aSMaxim Levitsky 	ftl->heads =  33;
6557d17c02aSMaxim Levitsky 	ftl->sectors = 63;
6567d17c02aSMaxim Levitsky 	return 0;
6577d17c02aSMaxim Levitsky }
6587d17c02aSMaxim Levitsky 
6597d17c02aSMaxim Levitsky /* Validate the CIS */
6607d17c02aSMaxim Levitsky static int sm_read_cis(struct sm_ftl *ftl)
6617d17c02aSMaxim Levitsky {
6627d17c02aSMaxim Levitsky 	struct sm_oob oob;
6637d17c02aSMaxim Levitsky 
6647d17c02aSMaxim Levitsky 	if (sm_read_sector(ftl,
6657d17c02aSMaxim Levitsky 		0, ftl->cis_block, ftl->cis_boffset, ftl->cis_buffer, &oob))
6667d17c02aSMaxim Levitsky 			return -EIO;
6677d17c02aSMaxim Levitsky 
6687d17c02aSMaxim Levitsky 	if (!sm_sector_valid(&oob) || !sm_block_valid(&oob))
6697d17c02aSMaxim Levitsky 		return -EIO;
6707d17c02aSMaxim Levitsky 
6717d17c02aSMaxim Levitsky 	if (!memcmp(ftl->cis_buffer + ftl->cis_page_offset,
6727d17c02aSMaxim Levitsky 			cis_signature, sizeof(cis_signature))) {
6737d17c02aSMaxim Levitsky 		return 0;
6747d17c02aSMaxim Levitsky 	}
6757d17c02aSMaxim Levitsky 
6767d17c02aSMaxim Levitsky 	return -EIO;
6777d17c02aSMaxim Levitsky }
6787d17c02aSMaxim Levitsky 
6797d17c02aSMaxim Levitsky /* Scan the media for the CIS */
6807d17c02aSMaxim Levitsky static int sm_find_cis(struct sm_ftl *ftl)
6817d17c02aSMaxim Levitsky {
6827d17c02aSMaxim Levitsky 	struct sm_oob oob;
6837d17c02aSMaxim Levitsky 	int block, boffset;
6847d17c02aSMaxim Levitsky 	int block_found = 0;
6857d17c02aSMaxim Levitsky 	int cis_found = 0;
6867d17c02aSMaxim Levitsky 
6877d17c02aSMaxim Levitsky 	/* Search for first valid block */
6887d17c02aSMaxim Levitsky 	for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) {
6897d17c02aSMaxim Levitsky 
6907d17c02aSMaxim Levitsky 		if (sm_read_sector(ftl, 0, block, 0, NULL, &oob))
6917d17c02aSMaxim Levitsky 			continue;
6927d17c02aSMaxim Levitsky 
6937d17c02aSMaxim Levitsky 		if (!sm_block_valid(&oob))
6947d17c02aSMaxim Levitsky 			continue;
6957d17c02aSMaxim Levitsky 		block_found = 1;
6967d17c02aSMaxim Levitsky 		break;
6977d17c02aSMaxim Levitsky 	}
6987d17c02aSMaxim Levitsky 
6997d17c02aSMaxim Levitsky 	if (!block_found)
7007d17c02aSMaxim Levitsky 		return -EIO;
7017d17c02aSMaxim Levitsky 
7027d17c02aSMaxim Levitsky 	/* Search for first valid sector in this block */
7037d17c02aSMaxim Levitsky 	for (boffset = 0 ; boffset < ftl->block_size;
7047d17c02aSMaxim Levitsky 						boffset += SM_SECTOR_SIZE) {
7057d17c02aSMaxim Levitsky 
7067d17c02aSMaxim Levitsky 		if (sm_read_sector(ftl, 0, block, boffset, NULL, &oob))
7077d17c02aSMaxim Levitsky 			continue;
7087d17c02aSMaxim Levitsky 
7097d17c02aSMaxim Levitsky 		if (!sm_sector_valid(&oob))
7107d17c02aSMaxim Levitsky 			continue;
7117d17c02aSMaxim Levitsky 		break;
7127d17c02aSMaxim Levitsky 	}
7137d17c02aSMaxim Levitsky 
7147d17c02aSMaxim Levitsky 	if (boffset == ftl->block_size)
7157d17c02aSMaxim Levitsky 		return -EIO;
7167d17c02aSMaxim Levitsky 
7177d17c02aSMaxim Levitsky 	ftl->cis_block = block;
7187d17c02aSMaxim Levitsky 	ftl->cis_boffset = boffset;
7197d17c02aSMaxim Levitsky 	ftl->cis_page_offset = 0;
7207d17c02aSMaxim Levitsky 
7217d17c02aSMaxim Levitsky 	cis_found = !sm_read_cis(ftl);
7227d17c02aSMaxim Levitsky 
7237d17c02aSMaxim Levitsky 	if (!cis_found) {
7247d17c02aSMaxim Levitsky 		ftl->cis_page_offset = SM_SMALL_PAGE;
7257d17c02aSMaxim Levitsky 		cis_found = !sm_read_cis(ftl);
7267d17c02aSMaxim Levitsky 	}
7277d17c02aSMaxim Levitsky 
7287d17c02aSMaxim Levitsky 	if (cis_found) {
7297d17c02aSMaxim Levitsky 		dbg("CIS block found at offset %x",
7307d17c02aSMaxim Levitsky 			block * ftl->block_size +
7317d17c02aSMaxim Levitsky 				boffset + ftl->cis_page_offset);
7327d17c02aSMaxim Levitsky 		return 0;
7337d17c02aSMaxim Levitsky 	}
7347d17c02aSMaxim Levitsky 	return -EIO;
7357d17c02aSMaxim Levitsky }
7367d17c02aSMaxim Levitsky 
7377d17c02aSMaxim Levitsky /* Basic test to determine if underlying mtd device if functional */
7387d17c02aSMaxim Levitsky static int sm_recheck_media(struct sm_ftl *ftl)
7397d17c02aSMaxim Levitsky {
7407d17c02aSMaxim Levitsky 	if (sm_read_cis(ftl)) {
7417d17c02aSMaxim Levitsky 
7427d17c02aSMaxim Levitsky 		if (!ftl->unstable) {
7437d17c02aSMaxim Levitsky 			sm_printk("media unstable, not allowing writes");
7447d17c02aSMaxim Levitsky 			ftl->unstable = 1;
7457d17c02aSMaxim Levitsky 		}
7467d17c02aSMaxim Levitsky 		return -EIO;
7477d17c02aSMaxim Levitsky 	}
7487d17c02aSMaxim Levitsky 	return 0;
7497d17c02aSMaxim Levitsky }
7507d17c02aSMaxim Levitsky 
7517d17c02aSMaxim Levitsky /* Initialize a FTL zone */
7527d17c02aSMaxim Levitsky static int sm_init_zone(struct sm_ftl *ftl, int zone_num)
7537d17c02aSMaxim Levitsky {
7547d17c02aSMaxim Levitsky 	struct ftl_zone *zone = &ftl->zones[zone_num];
7557d17c02aSMaxim Levitsky 	struct sm_oob oob;
7567d17c02aSMaxim Levitsky 	uint16_t block;
7577d17c02aSMaxim Levitsky 	int lba;
7587d17c02aSMaxim Levitsky 	int i = 0;
759133fa8c7SMaxim Levitsky 	int len;
7607d17c02aSMaxim Levitsky 
7617d17c02aSMaxim Levitsky 	dbg("initializing zone %d", zone_num);
7627d17c02aSMaxim Levitsky 
7637d17c02aSMaxim Levitsky 	/* Allocate memory for FTL table */
7646da2ec56SKees Cook 	zone->lba_to_phys_table = kmalloc_array(ftl->max_lba, 2, GFP_KERNEL);
7657d17c02aSMaxim Levitsky 
7667d17c02aSMaxim Levitsky 	if (!zone->lba_to_phys_table)
7677d17c02aSMaxim Levitsky 		return -ENOMEM;
7687d17c02aSMaxim Levitsky 	memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
7697d17c02aSMaxim Levitsky 
7707d17c02aSMaxim Levitsky 
7717d17c02aSMaxim Levitsky 	/* Allocate memory for free sectors FIFO */
7727d17c02aSMaxim Levitsky 	if (kfifo_alloc(&zone->free_sectors, ftl->zone_size * 2, GFP_KERNEL)) {
7737d17c02aSMaxim Levitsky 		kfree(zone->lba_to_phys_table);
7747d17c02aSMaxim Levitsky 		return -ENOMEM;
7757d17c02aSMaxim Levitsky 	}
7767d17c02aSMaxim Levitsky 
7777d17c02aSMaxim Levitsky 	/* Now scan the zone */
7787d17c02aSMaxim Levitsky 	for (block = 0 ; block < ftl->zone_size ; block++) {
7797d17c02aSMaxim Levitsky 
7807d17c02aSMaxim Levitsky 		/* Skip blocks till the CIS (including) */
7817d17c02aSMaxim Levitsky 		if (zone_num == 0 && block <= ftl->cis_block)
7827d17c02aSMaxim Levitsky 			continue;
7837d17c02aSMaxim Levitsky 
7847d17c02aSMaxim Levitsky 		/* Read the oob of first sector */
785137e92fdSWenwen Wang 		if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
786137e92fdSWenwen Wang 			kfifo_free(&zone->free_sectors);
787137e92fdSWenwen Wang 			kfree(zone->lba_to_phys_table);
7887d17c02aSMaxim Levitsky 			return -EIO;
789137e92fdSWenwen Wang 		}
7907d17c02aSMaxim Levitsky 
7917d17c02aSMaxim Levitsky 		/* Test to see if block is erased. It is enough to test
792cc9d663aSShubhankar Kuranagatti 		 * first sector, because erase happens in one shot
793cc9d663aSShubhankar Kuranagatti 		 */
7947d17c02aSMaxim Levitsky 		if (sm_block_erased(&oob)) {
7957d17c02aSMaxim Levitsky 			kfifo_in(&zone->free_sectors,
7967d17c02aSMaxim Levitsky 				(unsigned char *)&block, 2);
7977d17c02aSMaxim Levitsky 			continue;
7987d17c02aSMaxim Levitsky 		}
7997d17c02aSMaxim Levitsky 
8007d17c02aSMaxim Levitsky 		/* If block is marked as bad, skip it */
8017d17c02aSMaxim Levitsky 		/* This assumes we can trust first sector*/
8027d17c02aSMaxim Levitsky 		/* However the way the block valid status is defined, ensures
803cc9d663aSShubhankar Kuranagatti 		 * very low probability of failure here
804cc9d663aSShubhankar Kuranagatti 		 */
8057d17c02aSMaxim Levitsky 		if (!sm_block_valid(&oob)) {
8067d17c02aSMaxim Levitsky 			dbg("PH %04d <-> <marked bad>", block);
8077d17c02aSMaxim Levitsky 			continue;
8087d17c02aSMaxim Levitsky 		}
8097d17c02aSMaxim Levitsky 
8107d17c02aSMaxim Levitsky 
8117d17c02aSMaxim Levitsky 		lba = sm_read_lba(&oob);
8127d17c02aSMaxim Levitsky 
8137d17c02aSMaxim Levitsky 		/* Invalid LBA means that block is damaged. */
8147d17c02aSMaxim Levitsky 		/* We can try to erase it, or mark it as bad, but
815cc9d663aSShubhankar Kuranagatti 		 * lets leave that to recovery application
816cc9d663aSShubhankar Kuranagatti 		 */
8177d17c02aSMaxim Levitsky 		if (lba == -2 || lba >= ftl->max_lba) {
8187d17c02aSMaxim Levitsky 			dbg("PH %04d <-> LBA %04d(bad)", block, lba);
8197d17c02aSMaxim Levitsky 			continue;
8207d17c02aSMaxim Levitsky 		}
8217d17c02aSMaxim Levitsky 
8227d17c02aSMaxim Levitsky 
8237d17c02aSMaxim Levitsky 		/* If there is no collision,
824cc9d663aSShubhankar Kuranagatti 		 * just put the sector in the FTL table
825cc9d663aSShubhankar Kuranagatti 		 */
8267d17c02aSMaxim Levitsky 		if (zone->lba_to_phys_table[lba] < 0) {
8277d17c02aSMaxim Levitsky 			dbg_verbose("PH %04d <-> LBA %04d", block, lba);
8287d17c02aSMaxim Levitsky 			zone->lba_to_phys_table[lba] = block;
8297d17c02aSMaxim Levitsky 			continue;
8307d17c02aSMaxim Levitsky 		}
8317d17c02aSMaxim Levitsky 
8327d17c02aSMaxim Levitsky 		sm_printk("collision"
8337d17c02aSMaxim Levitsky 			" of LBA %d between blocks %d and %d in zone %d",
8347d17c02aSMaxim Levitsky 			lba, zone->lba_to_phys_table[lba], block, zone_num);
8357d17c02aSMaxim Levitsky 
8367d17c02aSMaxim Levitsky 		/* Test that this block is valid*/
8377d17c02aSMaxim Levitsky 		if (sm_check_block(ftl, zone_num, block))
8387d17c02aSMaxim Levitsky 			continue;
8397d17c02aSMaxim Levitsky 
8407d17c02aSMaxim Levitsky 		/* Test now the old block */
8417d17c02aSMaxim Levitsky 		if (sm_check_block(ftl, zone_num,
8427d17c02aSMaxim Levitsky 					zone->lba_to_phys_table[lba])) {
8437d17c02aSMaxim Levitsky 			zone->lba_to_phys_table[lba] = block;
8447d17c02aSMaxim Levitsky 			continue;
8457d17c02aSMaxim Levitsky 		}
8467d17c02aSMaxim Levitsky 
8477d17c02aSMaxim Levitsky 		/* If both blocks are valid and share same LBA, it means that
848cc9d663aSShubhankar Kuranagatti 		 * they hold different versions of same data. It not
849cc9d663aSShubhankar Kuranagatti 		 * known which is more recent, thus just erase one of them
8507d17c02aSMaxim Levitsky 		 */
8517d17c02aSMaxim Levitsky 		sm_printk("both blocks are valid, erasing the later");
8527d17c02aSMaxim Levitsky 		sm_erase_block(ftl, zone_num, block, 1);
8537d17c02aSMaxim Levitsky 	}
8547d17c02aSMaxim Levitsky 
8557d17c02aSMaxim Levitsky 	dbg("zone initialized");
8567d17c02aSMaxim Levitsky 	zone->initialized = 1;
8577d17c02aSMaxim Levitsky 
8587d17c02aSMaxim Levitsky 	/* No free sectors, means that the zone is heavily damaged, write won't
859cc9d663aSShubhankar Kuranagatti 	 * work, but it can still can be (partially) read
860cc9d663aSShubhankar Kuranagatti 	 */
8617d17c02aSMaxim Levitsky 	if (!kfifo_len(&zone->free_sectors)) {
8627d17c02aSMaxim Levitsky 		sm_printk("no free blocks in zone %d", zone_num);
8637d17c02aSMaxim Levitsky 		return 0;
8647d17c02aSMaxim Levitsky 	}
8657d17c02aSMaxim Levitsky 
8667d17c02aSMaxim Levitsky 	/* Randomize first block we write to */
8677d17c02aSMaxim Levitsky 	get_random_bytes(&i, 2);
8687d17c02aSMaxim Levitsky 	i %= (kfifo_len(&zone->free_sectors) / 2);
8697d17c02aSMaxim Levitsky 
8707d17c02aSMaxim Levitsky 	while (i--) {
871133fa8c7SMaxim Levitsky 		len = kfifo_out(&zone->free_sectors,
872133fa8c7SMaxim Levitsky 					(unsigned char *)&block, 2);
873133fa8c7SMaxim Levitsky 		WARN_ON(len != 2);
8747d17c02aSMaxim Levitsky 		kfifo_in(&zone->free_sectors, (const unsigned char *)&block, 2);
8757d17c02aSMaxim Levitsky 	}
8767d17c02aSMaxim Levitsky 	return 0;
8777d17c02aSMaxim Levitsky }
8787d17c02aSMaxim Levitsky 
87925985edcSLucas De Marchi /* Get and automatically initialize an FTL mapping for one zone */
880582b2ffcSJingoo Han static struct ftl_zone *sm_get_zone(struct sm_ftl *ftl, int zone_num)
8817d17c02aSMaxim Levitsky {
8827d17c02aSMaxim Levitsky 	struct ftl_zone *zone;
8837d17c02aSMaxim Levitsky 	int error;
8847d17c02aSMaxim Levitsky 
8857d17c02aSMaxim Levitsky 	BUG_ON(zone_num >= ftl->zone_count);
8867d17c02aSMaxim Levitsky 	zone = &ftl->zones[zone_num];
8877d17c02aSMaxim Levitsky 
8887d17c02aSMaxim Levitsky 	if (!zone->initialized) {
8897d17c02aSMaxim Levitsky 		error = sm_init_zone(ftl, zone_num);
8907d17c02aSMaxim Levitsky 
8917d17c02aSMaxim Levitsky 		if (error)
8927d17c02aSMaxim Levitsky 			return ERR_PTR(error);
8937d17c02aSMaxim Levitsky 	}
8947d17c02aSMaxim Levitsky 	return zone;
8957d17c02aSMaxim Levitsky }
8967d17c02aSMaxim Levitsky 
8977d17c02aSMaxim Levitsky 
8987d17c02aSMaxim Levitsky /* ----------------- cache handling ------------------------------------------*/
8997d17c02aSMaxim Levitsky 
9007d17c02aSMaxim Levitsky /* Initialize the one block cache */
901582b2ffcSJingoo Han static void sm_cache_init(struct sm_ftl *ftl)
9027d17c02aSMaxim Levitsky {
9037d17c02aSMaxim Levitsky 	ftl->cache_data_invalid_bitmap = 0xFFFFFFFF;
9047d17c02aSMaxim Levitsky 	ftl->cache_clean = 1;
9057d17c02aSMaxim Levitsky 	ftl->cache_zone = -1;
9067d17c02aSMaxim Levitsky 	ftl->cache_block = -1;
9077d17c02aSMaxim Levitsky 	/*memset(ftl->cache_data, 0xAA, ftl->block_size);*/
9087d17c02aSMaxim Levitsky }
9097d17c02aSMaxim Levitsky 
9107d17c02aSMaxim Levitsky /* Put sector in one block cache */
911582b2ffcSJingoo Han static void sm_cache_put(struct sm_ftl *ftl, char *buffer, int boffset)
9127d17c02aSMaxim Levitsky {
9137d17c02aSMaxim Levitsky 	memcpy(ftl->cache_data + boffset, buffer, SM_SECTOR_SIZE);
9147d17c02aSMaxim Levitsky 	clear_bit(boffset / SM_SECTOR_SIZE, &ftl->cache_data_invalid_bitmap);
9157d17c02aSMaxim Levitsky 	ftl->cache_clean = 0;
9167d17c02aSMaxim Levitsky }
9177d17c02aSMaxim Levitsky 
9187d17c02aSMaxim Levitsky /* Read a sector from the cache */
919582b2ffcSJingoo Han static int sm_cache_get(struct sm_ftl *ftl, char *buffer, int boffset)
9207d17c02aSMaxim Levitsky {
9217d17c02aSMaxim Levitsky 	if (test_bit(boffset / SM_SECTOR_SIZE,
9227d17c02aSMaxim Levitsky 		&ftl->cache_data_invalid_bitmap))
9237d17c02aSMaxim Levitsky 			return -1;
9247d17c02aSMaxim Levitsky 
9257d17c02aSMaxim Levitsky 	memcpy(buffer, ftl->cache_data + boffset, SM_SECTOR_SIZE);
9267d17c02aSMaxim Levitsky 	return 0;
9277d17c02aSMaxim Levitsky }
9287d17c02aSMaxim Levitsky 
9297d17c02aSMaxim Levitsky /* Write the cache to hardware */
930582b2ffcSJingoo Han static int sm_cache_flush(struct sm_ftl *ftl)
9317d17c02aSMaxim Levitsky {
9327d17c02aSMaxim Levitsky 	struct ftl_zone *zone;
9337d17c02aSMaxim Levitsky 
9347d17c02aSMaxim Levitsky 	int sector_num;
9357d17c02aSMaxim Levitsky 	uint16_t write_sector;
9367d17c02aSMaxim Levitsky 	int zone_num = ftl->cache_zone;
9377d17c02aSMaxim Levitsky 	int block_num;
9387d17c02aSMaxim Levitsky 
9397d17c02aSMaxim Levitsky 	if (ftl->cache_clean)
9407d17c02aSMaxim Levitsky 		return 0;
9417d17c02aSMaxim Levitsky 
9427d17c02aSMaxim Levitsky 	if (ftl->unstable)
9437d17c02aSMaxim Levitsky 		return -EIO;
9447d17c02aSMaxim Levitsky 
9457d17c02aSMaxim Levitsky 	BUG_ON(zone_num < 0);
9467d17c02aSMaxim Levitsky 	zone = &ftl->zones[zone_num];
9477d17c02aSMaxim Levitsky 	block_num = zone->lba_to_phys_table[ftl->cache_block];
9487d17c02aSMaxim Levitsky 
9497d17c02aSMaxim Levitsky 
9507d17c02aSMaxim Levitsky 	/* Try to read all unread areas of the cache block*/
951fed457a8SAkinobu Mita 	for_each_set_bit(sector_num, &ftl->cache_data_invalid_bitmap,
9527d17c02aSMaxim Levitsky 		ftl->block_size / SM_SECTOR_SIZE) {
9537d17c02aSMaxim Levitsky 
9547d17c02aSMaxim Levitsky 		if (!sm_read_sector(ftl,
9557d17c02aSMaxim Levitsky 			zone_num, block_num, sector_num * SM_SECTOR_SIZE,
9567d17c02aSMaxim Levitsky 			ftl->cache_data + sector_num * SM_SECTOR_SIZE, NULL))
9577d17c02aSMaxim Levitsky 				clear_bit(sector_num,
9587d17c02aSMaxim Levitsky 					&ftl->cache_data_invalid_bitmap);
9597d17c02aSMaxim Levitsky 	}
9607d17c02aSMaxim Levitsky restart:
9617d17c02aSMaxim Levitsky 
9627d17c02aSMaxim Levitsky 	if (ftl->unstable)
9637d17c02aSMaxim Levitsky 		return -EIO;
964133fa8c7SMaxim Levitsky 
965133fa8c7SMaxim Levitsky 	/* If there are no spare blocks, */
966133fa8c7SMaxim Levitsky 	/* we could still continue by erasing/writing the current block,
967cc9d663aSShubhankar Kuranagatti 	 * but for such worn out media it doesn't worth the trouble,
968cc9d663aSShubhankar Kuranagatti 	 * and the dangers
969cc9d663aSShubhankar Kuranagatti 	 */
970133fa8c7SMaxim Levitsky 	if (kfifo_out(&zone->free_sectors,
971133fa8c7SMaxim Levitsky 				(unsigned char *)&write_sector, 2) != 2) {
9727d17c02aSMaxim Levitsky 		dbg("no free sectors for write!");
9737d17c02aSMaxim Levitsky 		return -EIO;
9747d17c02aSMaxim Levitsky 	}
9757d17c02aSMaxim Levitsky 
9767d17c02aSMaxim Levitsky 
9777d17c02aSMaxim Levitsky 	if (sm_write_block(ftl, ftl->cache_data, zone_num, write_sector,
9787d17c02aSMaxim Levitsky 		ftl->cache_block, ftl->cache_data_invalid_bitmap))
9797d17c02aSMaxim Levitsky 			goto restart;
9807d17c02aSMaxim Levitsky 
9817d17c02aSMaxim Levitsky 	/* Update the FTL table */
9827d17c02aSMaxim Levitsky 	zone->lba_to_phys_table[ftl->cache_block] = write_sector;
9837d17c02aSMaxim Levitsky 
9847d17c02aSMaxim Levitsky 	/* Write succesfull, so erase and free the old block */
9857d17c02aSMaxim Levitsky 	if (block_num > 0)
9867d17c02aSMaxim Levitsky 		sm_erase_block(ftl, zone_num, block_num, 1);
9877d17c02aSMaxim Levitsky 
9887d17c02aSMaxim Levitsky 	sm_cache_init(ftl);
9897d17c02aSMaxim Levitsky 	return 0;
9907d17c02aSMaxim Levitsky }
9917d17c02aSMaxim Levitsky 
9927d17c02aSMaxim Levitsky 
9937d17c02aSMaxim Levitsky /* flush timer, runs a second after last write */
994e99e88a9SKees Cook static void sm_cache_flush_timer(struct timer_list *t)
9957d17c02aSMaxim Levitsky {
996e99e88a9SKees Cook 	struct sm_ftl *ftl = from_timer(ftl, t, timer);
9977d17c02aSMaxim Levitsky 	queue_work(cache_flush_workqueue, &ftl->flush_work);
9987d17c02aSMaxim Levitsky }
9997d17c02aSMaxim Levitsky 
10007d17c02aSMaxim Levitsky /* cache flush work, kicked by timer */
10017d17c02aSMaxim Levitsky static void sm_cache_flush_work(struct work_struct *work)
10027d17c02aSMaxim Levitsky {
10037d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = container_of(work, struct sm_ftl, flush_work);
10047d17c02aSMaxim Levitsky 	mutex_lock(&ftl->mutex);
10057d17c02aSMaxim Levitsky 	sm_cache_flush(ftl);
10067d17c02aSMaxim Levitsky 	mutex_unlock(&ftl->mutex);
10077d17c02aSMaxim Levitsky 	return;
10087d17c02aSMaxim Levitsky }
10097d17c02aSMaxim Levitsky 
10107d17c02aSMaxim Levitsky /* ---------------- outside interface -------------------------------------- */
10117d17c02aSMaxim Levitsky 
10127d17c02aSMaxim Levitsky /* outside interface: read a sector */
10137d17c02aSMaxim Levitsky static int sm_read(struct mtd_blktrans_dev *dev,
10147d17c02aSMaxim Levitsky 		   unsigned long sect_no, char *buf)
10157d17c02aSMaxim Levitsky {
10167d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
10177d17c02aSMaxim Levitsky 	struct ftl_zone *zone;
10187d17c02aSMaxim Levitsky 	int error = 0, in_cache = 0;
10197d17c02aSMaxim Levitsky 	int zone_num, block, boffset;
10207d17c02aSMaxim Levitsky 
10217d17c02aSMaxim Levitsky 	sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
10227d17c02aSMaxim Levitsky 	mutex_lock(&ftl->mutex);
10237d17c02aSMaxim Levitsky 
10247d17c02aSMaxim Levitsky 
10257d17c02aSMaxim Levitsky 	zone = sm_get_zone(ftl, zone_num);
10267d17c02aSMaxim Levitsky 	if (IS_ERR(zone)) {
10277d17c02aSMaxim Levitsky 		error = PTR_ERR(zone);
10287d17c02aSMaxim Levitsky 		goto unlock;
10297d17c02aSMaxim Levitsky 	}
10307d17c02aSMaxim Levitsky 
10317d17c02aSMaxim Levitsky 	/* Have to look at cache first */
10327d17c02aSMaxim Levitsky 	if (ftl->cache_zone == zone_num && ftl->cache_block == block) {
10337d17c02aSMaxim Levitsky 		in_cache = 1;
10347d17c02aSMaxim Levitsky 		if (!sm_cache_get(ftl, buf, boffset))
10357d17c02aSMaxim Levitsky 			goto unlock;
10367d17c02aSMaxim Levitsky 	}
10377d17c02aSMaxim Levitsky 
10387d17c02aSMaxim Levitsky 	/* Translate the block and return if doesn't exist in the table */
10397d17c02aSMaxim Levitsky 	block = zone->lba_to_phys_table[block];
10407d17c02aSMaxim Levitsky 
10417d17c02aSMaxim Levitsky 	if (block == -1) {
10427d17c02aSMaxim Levitsky 		memset(buf, 0xFF, SM_SECTOR_SIZE);
10437d17c02aSMaxim Levitsky 		goto unlock;
10447d17c02aSMaxim Levitsky 	}
10457d17c02aSMaxim Levitsky 
10467d17c02aSMaxim Levitsky 	if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
10477d17c02aSMaxim Levitsky 		error = -EIO;
10487d17c02aSMaxim Levitsky 		goto unlock;
10497d17c02aSMaxim Levitsky 	}
10507d17c02aSMaxim Levitsky 
10517d17c02aSMaxim Levitsky 	if (in_cache)
10527d17c02aSMaxim Levitsky 		sm_cache_put(ftl, buf, boffset);
10537d17c02aSMaxim Levitsky unlock:
10547d17c02aSMaxim Levitsky 	mutex_unlock(&ftl->mutex);
10557d17c02aSMaxim Levitsky 	return error;
10567d17c02aSMaxim Levitsky }
10577d17c02aSMaxim Levitsky 
10587d17c02aSMaxim Levitsky /* outside interface: write a sector */
10597d17c02aSMaxim Levitsky static int sm_write(struct mtd_blktrans_dev *dev,
10607d17c02aSMaxim Levitsky 				unsigned long sec_no, char *buf)
10617d17c02aSMaxim Levitsky {
10627d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
10637d17c02aSMaxim Levitsky 	struct ftl_zone *zone;
1064f7f0d358SBrian Norris 	int error = 0, zone_num, block, boffset;
10657d17c02aSMaxim Levitsky 
10667d17c02aSMaxim Levitsky 	BUG_ON(ftl->readonly);
10677d17c02aSMaxim Levitsky 	sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
10687d17c02aSMaxim Levitsky 
10697d17c02aSMaxim Levitsky 	/* No need in flush thread running now */
10707d17c02aSMaxim Levitsky 	del_timer(&ftl->timer);
10717d17c02aSMaxim Levitsky 	mutex_lock(&ftl->mutex);
10727d17c02aSMaxim Levitsky 
10737d17c02aSMaxim Levitsky 	zone = sm_get_zone(ftl, zone_num);
10747d17c02aSMaxim Levitsky 	if (IS_ERR(zone)) {
10757d17c02aSMaxim Levitsky 		error = PTR_ERR(zone);
10767d17c02aSMaxim Levitsky 		goto unlock;
10777d17c02aSMaxim Levitsky 	}
10787d17c02aSMaxim Levitsky 
10797d17c02aSMaxim Levitsky 	/* If entry is not in cache, flush it */
10807d17c02aSMaxim Levitsky 	if (ftl->cache_block != block || ftl->cache_zone != zone_num) {
10817d17c02aSMaxim Levitsky 
10827d17c02aSMaxim Levitsky 		error = sm_cache_flush(ftl);
10837d17c02aSMaxim Levitsky 		if (error)
10847d17c02aSMaxim Levitsky 			goto unlock;
10857d17c02aSMaxim Levitsky 
10867d17c02aSMaxim Levitsky 		ftl->cache_block = block;
10877d17c02aSMaxim Levitsky 		ftl->cache_zone = zone_num;
10887d17c02aSMaxim Levitsky 	}
10897d17c02aSMaxim Levitsky 
10907d17c02aSMaxim Levitsky 	sm_cache_put(ftl, buf, boffset);
10917d17c02aSMaxim Levitsky unlock:
10927d17c02aSMaxim Levitsky 	mod_timer(&ftl->timer, jiffies + msecs_to_jiffies(cache_timeout));
10937d17c02aSMaxim Levitsky 	mutex_unlock(&ftl->mutex);
10947d17c02aSMaxim Levitsky 	return error;
10957d17c02aSMaxim Levitsky }
10967d17c02aSMaxim Levitsky 
10977d17c02aSMaxim Levitsky /* outside interface: flush everything */
10987d17c02aSMaxim Levitsky static int sm_flush(struct mtd_blktrans_dev *dev)
10997d17c02aSMaxim Levitsky {
11007d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
11017d17c02aSMaxim Levitsky 	int retval;
11027d17c02aSMaxim Levitsky 
11037d17c02aSMaxim Levitsky 	mutex_lock(&ftl->mutex);
11047d17c02aSMaxim Levitsky 	retval =  sm_cache_flush(ftl);
11057d17c02aSMaxim Levitsky 	mutex_unlock(&ftl->mutex);
11067d17c02aSMaxim Levitsky 	return retval;
11077d17c02aSMaxim Levitsky }
11087d17c02aSMaxim Levitsky 
11097d17c02aSMaxim Levitsky /* outside interface: device is released */
1110a8ca889eSAl Viro static void sm_release(struct mtd_blktrans_dev *dev)
11117d17c02aSMaxim Levitsky {
11127d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
11137d17c02aSMaxim Levitsky 
11147d17c02aSMaxim Levitsky 	del_timer_sync(&ftl->timer);
11157d17c02aSMaxim Levitsky 	cancel_work_sync(&ftl->flush_work);
1116a61528d9SDuoming Zhou 	mutex_lock(&ftl->mutex);
11177d17c02aSMaxim Levitsky 	sm_cache_flush(ftl);
11187d17c02aSMaxim Levitsky 	mutex_unlock(&ftl->mutex);
11197d17c02aSMaxim Levitsky }
11207d17c02aSMaxim Levitsky 
11217d17c02aSMaxim Levitsky /* outside interface: get geometry */
11227d17c02aSMaxim Levitsky static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
11237d17c02aSMaxim Levitsky {
11247d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
11257d17c02aSMaxim Levitsky 	geo->heads = ftl->heads;
11267d17c02aSMaxim Levitsky 	geo->sectors = ftl->sectors;
11277d17c02aSMaxim Levitsky 	geo->cylinders = ftl->cylinders;
11287d17c02aSMaxim Levitsky 	return 0;
11297d17c02aSMaxim Levitsky }
11307d17c02aSMaxim Levitsky 
11317d17c02aSMaxim Levitsky /* external interface: main initialization function */
11327d17c02aSMaxim Levitsky static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
11337d17c02aSMaxim Levitsky {
11347d17c02aSMaxim Levitsky 	struct mtd_blktrans_dev *trans;
11357d17c02aSMaxim Levitsky 	struct sm_ftl *ftl;
11367d17c02aSMaxim Levitsky 
11377d17c02aSMaxim Levitsky 	/* Allocate & initialize our private structure */
11387d17c02aSMaxim Levitsky 	ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL);
11397d17c02aSMaxim Levitsky 	if (!ftl)
11407d17c02aSMaxim Levitsky 		goto error1;
11417d17c02aSMaxim Levitsky 
11427d17c02aSMaxim Levitsky 
11437d17c02aSMaxim Levitsky 	mutex_init(&ftl->mutex);
1144e99e88a9SKees Cook 	timer_setup(&ftl->timer, sm_cache_flush_timer, 0);
11457d17c02aSMaxim Levitsky 	INIT_WORK(&ftl->flush_work, sm_cache_flush_work);
11467d17c02aSMaxim Levitsky 
11477d17c02aSMaxim Levitsky 	/* Read media information */
11487d17c02aSMaxim Levitsky 	if (sm_get_media_info(ftl, mtd)) {
11497d17c02aSMaxim Levitsky 		dbg("found unsupported mtd device, aborting");
11507d17c02aSMaxim Levitsky 		goto error2;
11517d17c02aSMaxim Levitsky 	}
11527d17c02aSMaxim Levitsky 
11537d17c02aSMaxim Levitsky 
11547d17c02aSMaxim Levitsky 	/* Allocate temporary CIS buffer for read retry support */
11557d17c02aSMaxim Levitsky 	ftl->cis_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL);
11567d17c02aSMaxim Levitsky 	if (!ftl->cis_buffer)
11577d17c02aSMaxim Levitsky 		goto error2;
11587d17c02aSMaxim Levitsky 
11597d17c02aSMaxim Levitsky 	/* Allocate zone array, it will be initialized on demand */
11606396bb22SKees Cook 	ftl->zones = kcalloc(ftl->zone_count, sizeof(struct ftl_zone),
11617d17c02aSMaxim Levitsky 								GFP_KERNEL);
11627d17c02aSMaxim Levitsky 	if (!ftl->zones)
11637d17c02aSMaxim Levitsky 		goto error3;
11647d17c02aSMaxim Levitsky 
11657d17c02aSMaxim Levitsky 	/* Allocate the cache*/
11667d17c02aSMaxim Levitsky 	ftl->cache_data = kzalloc(ftl->block_size, GFP_KERNEL);
11677d17c02aSMaxim Levitsky 
11687d17c02aSMaxim Levitsky 	if (!ftl->cache_data)
11697d17c02aSMaxim Levitsky 		goto error4;
11707d17c02aSMaxim Levitsky 
11717d17c02aSMaxim Levitsky 	sm_cache_init(ftl);
11727d17c02aSMaxim Levitsky 
11737d17c02aSMaxim Levitsky 
11747d17c02aSMaxim Levitsky 	/* Allocate upper layer structure and initialize it */
11757d17c02aSMaxim Levitsky 	trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
11767d17c02aSMaxim Levitsky 	if (!trans)
11777d17c02aSMaxim Levitsky 		goto error5;
11787d17c02aSMaxim Levitsky 
11797d17c02aSMaxim Levitsky 	ftl->trans = trans;
11807d17c02aSMaxim Levitsky 	trans->priv = ftl;
11817d17c02aSMaxim Levitsky 
11827d17c02aSMaxim Levitsky 	trans->tr = tr;
11837d17c02aSMaxim Levitsky 	trans->mtd = mtd;
11847d17c02aSMaxim Levitsky 	trans->devnum = -1;
11857d17c02aSMaxim Levitsky 	trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9;
11867d17c02aSMaxim Levitsky 	trans->readonly = ftl->readonly;
11877d17c02aSMaxim Levitsky 
11887d17c02aSMaxim Levitsky 	if (sm_find_cis(ftl)) {
11897d17c02aSMaxim Levitsky 		dbg("CIS not found on mtd device, aborting");
11907d17c02aSMaxim Levitsky 		goto error6;
11917d17c02aSMaxim Levitsky 	}
11927d17c02aSMaxim Levitsky 
11937d17c02aSMaxim Levitsky 	ftl->disk_attributes = sm_create_sysfs_attributes(ftl);
1194629286b9SXiaochen Wang 	if (!ftl->disk_attributes)
1195629286b9SXiaochen Wang 		goto error6;
11967d17c02aSMaxim Levitsky 	trans->disk_attributes = ftl->disk_attributes;
11977d17c02aSMaxim Levitsky 
11987d17c02aSMaxim Levitsky 	sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d",
11997d17c02aSMaxim Levitsky 		(int)(mtd->size / (1024 * 1024)), mtd->index);
12007d17c02aSMaxim Levitsky 
12017d17c02aSMaxim Levitsky 	dbg("FTL layout:");
12027d17c02aSMaxim Levitsky 	dbg("%d zone(s), each consists of %d blocks (+%d spares)",
12037d17c02aSMaxim Levitsky 		ftl->zone_count, ftl->max_lba,
12047d17c02aSMaxim Levitsky 		ftl->zone_size - ftl->max_lba);
12057d17c02aSMaxim Levitsky 	dbg("each block consists of %d bytes",
12067d17c02aSMaxim Levitsky 		ftl->block_size);
12077d17c02aSMaxim Levitsky 
12087d17c02aSMaxim Levitsky 
12097d17c02aSMaxim Levitsky 	/* Register device*/
12107d17c02aSMaxim Levitsky 	if (add_mtd_blktrans_dev(trans)) {
12117d17c02aSMaxim Levitsky 		dbg("error in mtdblktrans layer");
12127d17c02aSMaxim Levitsky 		goto error6;
12137d17c02aSMaxim Levitsky 	}
12147d17c02aSMaxim Levitsky 	return;
12157d17c02aSMaxim Levitsky error6:
12167d17c02aSMaxim Levitsky 	kfree(trans);
12177d17c02aSMaxim Levitsky error5:
12187d17c02aSMaxim Levitsky 	kfree(ftl->cache_data);
12197d17c02aSMaxim Levitsky error4:
12207d17c02aSMaxim Levitsky 	kfree(ftl->zones);
12217d17c02aSMaxim Levitsky error3:
12227d17c02aSMaxim Levitsky 	kfree(ftl->cis_buffer);
12237d17c02aSMaxim Levitsky error2:
12247d17c02aSMaxim Levitsky 	kfree(ftl);
12257d17c02aSMaxim Levitsky error1:
12267d17c02aSMaxim Levitsky 	return;
12277d17c02aSMaxim Levitsky }
12287d17c02aSMaxim Levitsky 
12297d17c02aSMaxim Levitsky /* main interface: device {surprise,} removal */
12307d17c02aSMaxim Levitsky static void sm_remove_dev(struct mtd_blktrans_dev *dev)
12317d17c02aSMaxim Levitsky {
12327d17c02aSMaxim Levitsky 	struct sm_ftl *ftl = dev->priv;
12337d17c02aSMaxim Levitsky 	int i;
12347d17c02aSMaxim Levitsky 
12357d17c02aSMaxim Levitsky 	del_mtd_blktrans_dev(dev);
12367d17c02aSMaxim Levitsky 	ftl->trans = NULL;
12377d17c02aSMaxim Levitsky 
12387d17c02aSMaxim Levitsky 	for (i = 0 ; i < ftl->zone_count; i++) {
12397d17c02aSMaxim Levitsky 
12407d17c02aSMaxim Levitsky 		if (!ftl->zones[i].initialized)
12417d17c02aSMaxim Levitsky 			continue;
12427d17c02aSMaxim Levitsky 
12437d17c02aSMaxim Levitsky 		kfree(ftl->zones[i].lba_to_phys_table);
12447d17c02aSMaxim Levitsky 		kfifo_free(&ftl->zones[i].free_sectors);
12457d17c02aSMaxim Levitsky 	}
12467d17c02aSMaxim Levitsky 
12477d17c02aSMaxim Levitsky 	sm_delete_sysfs_attributes(ftl);
12487d17c02aSMaxim Levitsky 	kfree(ftl->cis_buffer);
12497d17c02aSMaxim Levitsky 	kfree(ftl->zones);
12507d17c02aSMaxim Levitsky 	kfree(ftl->cache_data);
12517d17c02aSMaxim Levitsky 	kfree(ftl);
12527d17c02aSMaxim Levitsky }
12537d17c02aSMaxim Levitsky 
12547d17c02aSMaxim Levitsky static struct mtd_blktrans_ops sm_ftl_ops = {
12557d17c02aSMaxim Levitsky 	.name		= "smblk",
1256452380efSMaxim Levitsky 	.major		= 0,
12577d17c02aSMaxim Levitsky 	.part_bits	= SM_FTL_PARTN_BITS,
12587d17c02aSMaxim Levitsky 	.blksize	= SM_SECTOR_SIZE,
12597d17c02aSMaxim Levitsky 	.getgeo		= sm_getgeo,
12607d17c02aSMaxim Levitsky 
12617d17c02aSMaxim Levitsky 	.add_mtd	= sm_add_mtd,
12627d17c02aSMaxim Levitsky 	.remove_dev	= sm_remove_dev,
12637d17c02aSMaxim Levitsky 
12647d17c02aSMaxim Levitsky 	.readsect	= sm_read,
12657d17c02aSMaxim Levitsky 	.writesect	= sm_write,
12667d17c02aSMaxim Levitsky 
12677d17c02aSMaxim Levitsky 	.flush		= sm_flush,
12687d17c02aSMaxim Levitsky 	.release	= sm_release,
12697d17c02aSMaxim Levitsky 
12707d17c02aSMaxim Levitsky 	.owner		= THIS_MODULE,
12717d17c02aSMaxim Levitsky };
12727d17c02aSMaxim Levitsky 
12737d17c02aSMaxim Levitsky static __init int sm_module_init(void)
12747d17c02aSMaxim Levitsky {
12757d17c02aSMaxim Levitsky 	int error = 0;
12767d17c02aSMaxim Levitsky 
127739de86efSDan Carpenter 	cache_flush_workqueue = create_freezable_workqueue("smflush");
127839de86efSDan Carpenter 	if (!cache_flush_workqueue)
127939de86efSDan Carpenter 		return -ENOMEM;
12807d17c02aSMaxim Levitsky 
12817d17c02aSMaxim Levitsky 	error = register_mtd_blktrans(&sm_ftl_ops);
12827d17c02aSMaxim Levitsky 	if (error)
12837d17c02aSMaxim Levitsky 		destroy_workqueue(cache_flush_workqueue);
12847d17c02aSMaxim Levitsky 	return error;
12857d17c02aSMaxim Levitsky 
12867d17c02aSMaxim Levitsky }
12877d17c02aSMaxim Levitsky 
12887d17c02aSMaxim Levitsky static void __exit sm_module_exit(void)
12897d17c02aSMaxim Levitsky {
12907d17c02aSMaxim Levitsky 	destroy_workqueue(cache_flush_workqueue);
12917d17c02aSMaxim Levitsky 	deregister_mtd_blktrans(&sm_ftl_ops);
12927d17c02aSMaxim Levitsky }
12937d17c02aSMaxim Levitsky 
12947d17c02aSMaxim Levitsky module_init(sm_module_init);
12957d17c02aSMaxim Levitsky module_exit(sm_module_exit);
12967d17c02aSMaxim Levitsky 
12977d17c02aSMaxim Levitsky MODULE_LICENSE("GPL");
12987d17c02aSMaxim Levitsky MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
12997d17c02aSMaxim Levitsky MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer");
1300