xref: /openbmc/linux/drivers/mtd/ftl.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
11da177e4SLinus Torvalds /* This version ported to the Linux-MTD system by dwmw2@infradead.org
21da177e4SLinus Torvalds  *
31da177e4SLinus Torvalds  * Fixes: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
41da177e4SLinus Torvalds  * - fixes some leaks on failure in build_maps and ftl_notify_add, cleanups
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  * Based on:
71da177e4SLinus Torvalds  */
81da177e4SLinus Torvalds /*======================================================================
91da177e4SLinus Torvalds 
101da177e4SLinus Torvalds     A Flash Translation Layer memory card driver
111da177e4SLinus Torvalds 
121da177e4SLinus Torvalds     This driver implements a disk-like block device driver with an
131da177e4SLinus Torvalds     apparent block size of 512 bytes for flash memory cards.
141da177e4SLinus Torvalds 
151da177e4SLinus Torvalds     ftl_cs.c 1.62 2000/02/01 00:59:04
161da177e4SLinus Torvalds 
171da177e4SLinus Torvalds     The contents of this file are subject to the Mozilla Public
181da177e4SLinus Torvalds     License Version 1.1 (the "License"); you may not use this file
191da177e4SLinus Torvalds     except in compliance with the License. You may obtain a copy of
201da177e4SLinus Torvalds     the License at http://www.mozilla.org/MPL/
211da177e4SLinus Torvalds 
221da177e4SLinus Torvalds     Software distributed under the License is distributed on an "AS
231da177e4SLinus Torvalds     IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
241da177e4SLinus Torvalds     implied. See the License for the specific language governing
251da177e4SLinus Torvalds     rights and limitations under the License.
261da177e4SLinus Torvalds 
271da177e4SLinus Torvalds     The initial developer of the original code is David A. Hinds
281da177e4SLinus Torvalds     <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds
29a1452a37SDavid Woodhouse     are Copyright © 1999 David A. Hinds.  All Rights Reserved.
301da177e4SLinus Torvalds 
311da177e4SLinus Torvalds     Alternatively, the contents of this file may be used under the
321da177e4SLinus Torvalds     terms of the GNU General Public License version 2 (the "GPL"), in
331da177e4SLinus Torvalds     which case the provisions of the GPL are applicable instead of the
341da177e4SLinus Torvalds     above.  If you wish to allow the use of your version of this file
351da177e4SLinus Torvalds     only under the terms of the GPL and not to allow others to use
361da177e4SLinus Torvalds     your version of this file under the MPL, indicate your decision
371da177e4SLinus Torvalds     by deleting the provisions above and replace them with the notice
381da177e4SLinus Torvalds     and other provisions required by the GPL.  If you do not delete
391da177e4SLinus Torvalds     the provisions above, a recipient may use your version of this
401da177e4SLinus Torvalds     file under either the MPL or the GPL.
411da177e4SLinus Torvalds 
421da177e4SLinus Torvalds     LEGAL NOTE: The FTL format is patented by M-Systems.  They have
431da177e4SLinus Torvalds     granted a license for its use with PCMCIA devices:
441da177e4SLinus Torvalds 
451da177e4SLinus Torvalds      "M-Systems grants a royalty-free, non-exclusive license under
461da177e4SLinus Torvalds       any presently existing M-Systems intellectual property rights
471da177e4SLinus Torvalds       necessary for the design and development of FTL-compatible
481da177e4SLinus Torvalds       drivers, file systems and utilities using the data formats with
491da177e4SLinus Torvalds       PCMCIA PC Cards as described in the PCMCIA Flash Translation
501da177e4SLinus Torvalds       Layer (FTL) Specification."
511da177e4SLinus Torvalds 
521da177e4SLinus Torvalds     Use of the FTL format for non-PCMCIA applications may be an
531da177e4SLinus Torvalds     infringement of these patents.  For additional information,
54631dd1a8SJustin P. Mattock     contact M-Systems directly. M-Systems since acquired by Sandisk.
551da177e4SLinus Torvalds 
561da177e4SLinus Torvalds ======================================================================*/
571da177e4SLinus Torvalds #include <linux/mtd/blktrans.h>
581da177e4SLinus Torvalds #include <linux/module.h>
591da177e4SLinus Torvalds #include <linux/mtd/mtd.h>
601da177e4SLinus Torvalds /*#define PSYCHO_DEBUG */
611da177e4SLinus Torvalds 
621da177e4SLinus Torvalds #include <linux/kernel.h>
631da177e4SLinus Torvalds #include <linux/ptrace.h>
641da177e4SLinus Torvalds #include <linux/slab.h>
651da177e4SLinus Torvalds #include <linux/string.h>
661da177e4SLinus Torvalds #include <linux/timer.h>
671da177e4SLinus Torvalds #include <linux/major.h>
681da177e4SLinus Torvalds #include <linux/fs.h>
691da177e4SLinus Torvalds #include <linux/init.h>
701da177e4SLinus Torvalds #include <linux/hdreg.h>
711da177e4SLinus Torvalds #include <linux/vmalloc.h>
721da177e4SLinus Torvalds #include <linux/blkpg.h>
737c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
741da177e4SLinus Torvalds 
751da177e4SLinus Torvalds #include <linux/mtd/ftl.h>
761da177e4SLinus Torvalds 
771da177e4SLinus Torvalds /*====================================================================*/
781da177e4SLinus Torvalds 
791da177e4SLinus Torvalds /* Parameters that can be set with 'insmod' */
801da177e4SLinus Torvalds static int shuffle_freq = 50;
811da177e4SLinus Torvalds module_param(shuffle_freq, int, 0);
821da177e4SLinus Torvalds 
831da177e4SLinus Torvalds /*====================================================================*/
841da177e4SLinus Torvalds 
851da177e4SLinus Torvalds /* Major device # for FTL device */
861da177e4SLinus Torvalds #ifndef FTL_MAJOR
871da177e4SLinus Torvalds #define FTL_MAJOR	44
881da177e4SLinus Torvalds #endif
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds 
911da177e4SLinus Torvalds /*====================================================================*/
921da177e4SLinus Torvalds 
931da177e4SLinus Torvalds /* Maximum number of separate memory devices we'll allow */
941da177e4SLinus Torvalds #define MAX_DEV		4
951da177e4SLinus Torvalds 
961da177e4SLinus Torvalds /* Maximum number of regions per device */
971da177e4SLinus Torvalds #define MAX_REGION	4
981da177e4SLinus Torvalds 
991da177e4SLinus Torvalds /* Maximum number of partitions in an FTL region */
1001da177e4SLinus Torvalds #define PART_BITS	4
1011da177e4SLinus Torvalds 
1021da177e4SLinus Torvalds /* Maximum number of outstanding erase requests per socket */
1031da177e4SLinus Torvalds #define MAX_ERASE	8
1041da177e4SLinus Torvalds 
1051da177e4SLinus Torvalds /* Sector size -- shouldn't need to change */
1061da177e4SLinus Torvalds #define SECTOR_SIZE	512
1071da177e4SLinus Torvalds 
1081da177e4SLinus Torvalds 
1091da177e4SLinus Torvalds /* Each memory region corresponds to a minor device */
1101da177e4SLinus Torvalds typedef struct partition_t {
1111da177e4SLinus Torvalds     struct mtd_blktrans_dev mbd;
1123854be77SDavid Woodhouse     uint32_t		state;
1133854be77SDavid Woodhouse     uint32_t		*VirtualBlockMap;
1143854be77SDavid Woodhouse     uint32_t		FreeTotal;
1151da177e4SLinus Torvalds     struct eun_info_t {
1163854be77SDavid Woodhouse 	uint32_t		Offset;
1173854be77SDavid Woodhouse 	uint32_t		EraseCount;
1183854be77SDavid Woodhouse 	uint32_t		Free;
1193854be77SDavid Woodhouse 	uint32_t		Deleted;
1201da177e4SLinus Torvalds     } *EUNInfo;
1211da177e4SLinus Torvalds     struct xfer_info_t {
1223854be77SDavid Woodhouse 	uint32_t		Offset;
1233854be77SDavid Woodhouse 	uint32_t		EraseCount;
1243854be77SDavid Woodhouse 	uint16_t		state;
1251da177e4SLinus Torvalds     } *XferInfo;
1263854be77SDavid Woodhouse     uint16_t		bam_index;
1273854be77SDavid Woodhouse     uint32_t		*bam_cache;
1283854be77SDavid Woodhouse     uint16_t		DataUnits;
1293854be77SDavid Woodhouse     uint32_t		BlocksPerUnit;
1301da177e4SLinus Torvalds     erase_unit_header_t	header;
1311da177e4SLinus Torvalds } partition_t;
1321da177e4SLinus Torvalds 
1331da177e4SLinus Torvalds /* Partition state flags */
1341da177e4SLinus Torvalds #define FTL_FORMATTED	0x01
1351da177e4SLinus Torvalds 
1361da177e4SLinus Torvalds /* Transfer unit states */
1371da177e4SLinus Torvalds #define XFER_UNKNOWN	0x00
1381da177e4SLinus Torvalds #define XFER_ERASING	0x01
1391da177e4SLinus Torvalds #define XFER_ERASED	0x02
1401da177e4SLinus Torvalds #define XFER_PREPARED	0x03
1411da177e4SLinus Torvalds #define XFER_FAILED	0x04
1421da177e4SLinus Torvalds 
1431da177e4SLinus Torvalds /*======================================================================
1441da177e4SLinus Torvalds 
1451da177e4SLinus Torvalds     Scan_header() checks to see if a memory region contains an FTL
1461da177e4SLinus Torvalds     partition.  build_maps() reads all the erase unit headers, builds
1471da177e4SLinus Torvalds     the erase unit map, and then builds the virtual page map.
1481da177e4SLinus Torvalds 
1491da177e4SLinus Torvalds ======================================================================*/
1501da177e4SLinus Torvalds 
scan_header(partition_t * part)1511da177e4SLinus Torvalds static int scan_header(partition_t *part)
1521da177e4SLinus Torvalds {
1531da177e4SLinus Torvalds     erase_unit_header_t header;
1541da177e4SLinus Torvalds     loff_t offset, max_offset;
1551da177e4SLinus Torvalds     size_t ret;
1561da177e4SLinus Torvalds     int err;
1571da177e4SLinus Torvalds     part->header.FormattedSize = 0;
1581da177e4SLinus Torvalds     max_offset = (0x100000<part->mbd.mtd->size)?0x100000:part->mbd.mtd->size;
1591da177e4SLinus Torvalds     /* Search first megabyte for a valid FTL header */
1601da177e4SLinus Torvalds     for (offset = 0;
1611da177e4SLinus Torvalds 	 (offset + sizeof(header)) < max_offset;
1621da177e4SLinus Torvalds 	 offset += part->mbd.mtd->erasesize ? : 0x2000) {
1631da177e4SLinus Torvalds 
164329ad399SArtem Bityutskiy 	err = mtd_read(part->mbd.mtd, offset, sizeof(header), &ret,
1651da177e4SLinus Torvalds                        (unsigned char *)&header);
1661da177e4SLinus Torvalds 
1671da177e4SLinus Torvalds 	if (err)
1681da177e4SLinus Torvalds 	    return err;
1691da177e4SLinus Torvalds 
1701da177e4SLinus Torvalds 	if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break;
1711da177e4SLinus Torvalds     }
1721da177e4SLinus Torvalds 
1731da177e4SLinus Torvalds     if (offset == max_offset) {
1741da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: FTL header not found.\n");
1751da177e4SLinus Torvalds 	return -ENOENT;
1761da177e4SLinus Torvalds     }
1771da177e4SLinus Torvalds     if (header.BlockSize != 9 ||
1781da177e4SLinus Torvalds 	(header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) ||
1791da177e4SLinus Torvalds 	(header.NumTransferUnits >= le16_to_cpu(header.NumEraseUnits))) {
1801da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: FTL header corrupt!\n");
1811da177e4SLinus Torvalds 	return -1;
1821da177e4SLinus Torvalds     }
1831da177e4SLinus Torvalds     if ((1 << header.EraseUnitSize) != part->mbd.mtd->erasesize) {
1841da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl: FTL EraseUnitSize %x != MTD erasesize %x\n",
1851da177e4SLinus Torvalds 	       1 << header.EraseUnitSize,part->mbd.mtd->erasesize);
1861da177e4SLinus Torvalds 	return -1;
1871da177e4SLinus Torvalds     }
1881da177e4SLinus Torvalds     part->header = header;
1891da177e4SLinus Torvalds     return 0;
1901da177e4SLinus Torvalds }
1911da177e4SLinus Torvalds 
build_maps(partition_t * part)1921da177e4SLinus Torvalds static int build_maps(partition_t *part)
1931da177e4SLinus Torvalds {
1941da177e4SLinus Torvalds     erase_unit_header_t header;
1953854be77SDavid Woodhouse     uint16_t xvalid, xtrans, i;
1963854be77SDavid Woodhouse     unsigned blocks, j;
1971da177e4SLinus Torvalds     int hdr_ok, ret = -1;
1981da177e4SLinus Torvalds     ssize_t retval;
1991da177e4SLinus Torvalds     loff_t offset;
2001da177e4SLinus Torvalds 
2011da177e4SLinus Torvalds     /* Set up erase unit maps */
2021da177e4SLinus Torvalds     part->DataUnits = le16_to_cpu(part->header.NumEraseUnits) -
2031da177e4SLinus Torvalds 	part->header.NumTransferUnits;
2046da2ec56SKees Cook     part->EUNInfo = kmalloc_array(part->DataUnits, sizeof(struct eun_info_t),
2051da177e4SLinus Torvalds                                   GFP_KERNEL);
2061da177e4SLinus Torvalds     if (!part->EUNInfo)
2071da177e4SLinus Torvalds 	    goto out;
2081da177e4SLinus Torvalds     for (i = 0; i < part->DataUnits; i++)
2091da177e4SLinus Torvalds 	part->EUNInfo[i].Offset = 0xffffffff;
2101da177e4SLinus Torvalds     part->XferInfo =
2116da2ec56SKees Cook 	kmalloc_array(part->header.NumTransferUnits,
2126da2ec56SKees Cook                       sizeof(struct xfer_info_t),
2131da177e4SLinus Torvalds                       GFP_KERNEL);
2141da177e4SLinus Torvalds     if (!part->XferInfo)
2151da177e4SLinus Torvalds 	    goto out_EUNInfo;
2161da177e4SLinus Torvalds 
2171da177e4SLinus Torvalds     xvalid = xtrans = 0;
2181da177e4SLinus Torvalds     for (i = 0; i < le16_to_cpu(part->header.NumEraseUnits); i++) {
2191da177e4SLinus Torvalds 	offset = ((i + le16_to_cpu(part->header.FirstPhysicalEUN))
2201da177e4SLinus Torvalds 		      << part->header.EraseUnitSize);
221329ad399SArtem Bityutskiy 	ret = mtd_read(part->mbd.mtd, offset, sizeof(header), &retval,
2221da177e4SLinus Torvalds                        (unsigned char *)&header);
2231da177e4SLinus Torvalds 
2241da177e4SLinus Torvalds 	if (ret)
2251da177e4SLinus Torvalds 	    goto out_XferInfo;
2261da177e4SLinus Torvalds 
2271da177e4SLinus Torvalds 	ret = -1;
2281da177e4SLinus Torvalds 	/* Is this a transfer partition? */
2291da177e4SLinus Torvalds 	hdr_ok = (strcmp(header.DataOrgTuple+3, "FTL100") == 0);
2301da177e4SLinus Torvalds 	if (hdr_ok && (le16_to_cpu(header.LogicalEUN) < part->DataUnits) &&
2311da177e4SLinus Torvalds 	    (part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset == 0xffffffff)) {
2321da177e4SLinus Torvalds 	    part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset = offset;
2331da177e4SLinus Torvalds 	    part->EUNInfo[le16_to_cpu(header.LogicalEUN)].EraseCount =
2341da177e4SLinus Torvalds 		le32_to_cpu(header.EraseCount);
2351da177e4SLinus Torvalds 	    xvalid++;
2361da177e4SLinus Torvalds 	} else {
2371da177e4SLinus Torvalds 	    if (xtrans == part->header.NumTransferUnits) {
2381da177e4SLinus Torvalds 		printk(KERN_NOTICE "ftl_cs: format error: too many "
2391da177e4SLinus Torvalds 		       "transfer units!\n");
2401da177e4SLinus Torvalds 		goto out_XferInfo;
2411da177e4SLinus Torvalds 	    }
2421da177e4SLinus Torvalds 	    if (hdr_ok && (le16_to_cpu(header.LogicalEUN) == 0xffff)) {
2431da177e4SLinus Torvalds 		part->XferInfo[xtrans].state = XFER_PREPARED;
2441da177e4SLinus Torvalds 		part->XferInfo[xtrans].EraseCount = le32_to_cpu(header.EraseCount);
2451da177e4SLinus Torvalds 	    } else {
2461da177e4SLinus Torvalds 		part->XferInfo[xtrans].state = XFER_UNKNOWN;
2471da177e4SLinus Torvalds 		/* Pick anything reasonable for the erase count */
2481da177e4SLinus Torvalds 		part->XferInfo[xtrans].EraseCount =
2491da177e4SLinus Torvalds 		    le32_to_cpu(part->header.EraseCount);
2501da177e4SLinus Torvalds 	    }
2511da177e4SLinus Torvalds 	    part->XferInfo[xtrans].Offset = offset;
2521da177e4SLinus Torvalds 	    xtrans++;
2531da177e4SLinus Torvalds 	}
2541da177e4SLinus Torvalds     }
2551da177e4SLinus Torvalds     /* Check for format trouble */
2561da177e4SLinus Torvalds     header = part->header;
2571da177e4SLinus Torvalds     if ((xtrans != header.NumTransferUnits) ||
2581da177e4SLinus Torvalds 	(xvalid+xtrans != le16_to_cpu(header.NumEraseUnits))) {
2591da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: format error: erase units "
2601da177e4SLinus Torvalds 	       "don't add up!\n");
2611da177e4SLinus Torvalds 	goto out_XferInfo;
2621da177e4SLinus Torvalds     }
2631da177e4SLinus Torvalds 
2641da177e4SLinus Torvalds     /* Set up virtual page map */
2651da177e4SLinus Torvalds     blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
26642bc47b3SKees Cook     part->VirtualBlockMap = vmalloc(array_size(blocks, sizeof(uint32_t)));
2671da177e4SLinus Torvalds     if (!part->VirtualBlockMap)
2681da177e4SLinus Torvalds 	    goto out_XferInfo;
2691da177e4SLinus Torvalds 
2703854be77SDavid Woodhouse     memset(part->VirtualBlockMap, 0xff, blocks * sizeof(uint32_t));
2711da177e4SLinus Torvalds     part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
2721da177e4SLinus Torvalds 
2736da2ec56SKees Cook     part->bam_cache = kmalloc_array(part->BlocksPerUnit, sizeof(uint32_t),
2741da177e4SLinus Torvalds                                     GFP_KERNEL);
2751da177e4SLinus Torvalds     if (!part->bam_cache)
2761da177e4SLinus Torvalds 	    goto out_VirtualBlockMap;
2771da177e4SLinus Torvalds 
2781da177e4SLinus Torvalds     part->bam_index = 0xffff;
2791da177e4SLinus Torvalds     part->FreeTotal = 0;
2801da177e4SLinus Torvalds 
2811da177e4SLinus Torvalds     for (i = 0; i < part->DataUnits; i++) {
2821da177e4SLinus Torvalds 	part->EUNInfo[i].Free = 0;
2831da177e4SLinus Torvalds 	part->EUNInfo[i].Deleted = 0;
2841da177e4SLinus Torvalds 	offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset);
2851da177e4SLinus Torvalds 
286329ad399SArtem Bityutskiy 	ret = mtd_read(part->mbd.mtd, offset,
2873854be77SDavid Woodhouse                        part->BlocksPerUnit * sizeof(uint32_t), &retval,
2881da177e4SLinus Torvalds                        (unsigned char *)part->bam_cache);
2891da177e4SLinus Torvalds 
2901da177e4SLinus Torvalds 	if (ret)
2911da177e4SLinus Torvalds 		goto out_bam_cache;
2921da177e4SLinus Torvalds 
2931da177e4SLinus Torvalds 	for (j = 0; j < part->BlocksPerUnit; j++) {
2941da177e4SLinus Torvalds 	    if (BLOCK_FREE(le32_to_cpu(part->bam_cache[j]))) {
2951da177e4SLinus Torvalds 		part->EUNInfo[i].Free++;
2961da177e4SLinus Torvalds 		part->FreeTotal++;
2971da177e4SLinus Torvalds 	    } else if ((BLOCK_TYPE(le32_to_cpu(part->bam_cache[j])) == BLOCK_DATA) &&
2981da177e4SLinus Torvalds 		     (BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j])) < blocks))
2991da177e4SLinus Torvalds 		part->VirtualBlockMap[BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j]))] =
3001da177e4SLinus Torvalds 		    (i << header.EraseUnitSize) + (j << header.BlockSize);
3011da177e4SLinus Torvalds 	    else if (BLOCK_DELETED(le32_to_cpu(part->bam_cache[j])))
3021da177e4SLinus Torvalds 		part->EUNInfo[i].Deleted++;
3031da177e4SLinus Torvalds 	}
3041da177e4SLinus Torvalds     }
3051da177e4SLinus Torvalds 
3061da177e4SLinus Torvalds     ret = 0;
3071da177e4SLinus Torvalds     goto out;
3081da177e4SLinus Torvalds 
3091da177e4SLinus Torvalds out_bam_cache:
3101da177e4SLinus Torvalds     kfree(part->bam_cache);
3111da177e4SLinus Torvalds out_VirtualBlockMap:
3121da177e4SLinus Torvalds     vfree(part->VirtualBlockMap);
3131da177e4SLinus Torvalds out_XferInfo:
3141da177e4SLinus Torvalds     kfree(part->XferInfo);
3151da177e4SLinus Torvalds out_EUNInfo:
3161da177e4SLinus Torvalds     kfree(part->EUNInfo);
3171da177e4SLinus Torvalds out:
3181da177e4SLinus Torvalds     return ret;
3191da177e4SLinus Torvalds } /* build_maps */
3201da177e4SLinus Torvalds 
3211da177e4SLinus Torvalds /*======================================================================
3221da177e4SLinus Torvalds 
3231da177e4SLinus Torvalds     Erase_xfer() schedules an asynchronous erase operation for a
3241da177e4SLinus Torvalds     transfer unit.
3251da177e4SLinus Torvalds 
3261da177e4SLinus Torvalds ======================================================================*/
3271da177e4SLinus Torvalds 
erase_xfer(partition_t * part,uint16_t xfernum)3281da177e4SLinus Torvalds static int erase_xfer(partition_t *part,
3293854be77SDavid Woodhouse 		      uint16_t xfernum)
3301da177e4SLinus Torvalds {
3311da177e4SLinus Torvalds     int ret;
3321da177e4SLinus Torvalds     struct xfer_info_t *xfer;
3331da177e4SLinus Torvalds     struct erase_info *erase;
3341da177e4SLinus Torvalds 
3351da177e4SLinus Torvalds     xfer = &part->XferInfo[xfernum];
336289c0522SBrian Norris     pr_debug("ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset);
3371da177e4SLinus Torvalds     xfer->state = XFER_ERASING;
3381da177e4SLinus Torvalds 
3391da177e4SLinus Torvalds     /* Is there a free erase slot? Always in MTD. */
3401da177e4SLinus Torvalds 
3411da177e4SLinus Torvalds 
3421da177e4SLinus Torvalds     erase=kmalloc(sizeof(struct erase_info), GFP_KERNEL);
3431da177e4SLinus Torvalds     if (!erase)
3441da177e4SLinus Torvalds             return -ENOMEM;
3451da177e4SLinus Torvalds 
3461da177e4SLinus Torvalds     erase->addr = xfer->Offset;
3471da177e4SLinus Torvalds     erase->len = 1 << part->header.EraseUnitSize;
3481da177e4SLinus Torvalds 
3497e1f0dc0SArtem Bityutskiy     ret = mtd_erase(part->mbd.mtd, erase);
350884cfd90SBoris Brezillon     if (!ret) {
351884cfd90SBoris Brezillon 	xfer->state = XFER_ERASED;
3521da177e4SLinus Torvalds 	xfer->EraseCount++;
353884cfd90SBoris Brezillon     } else {
354884cfd90SBoris Brezillon 	xfer->state = XFER_FAILED;
355884cfd90SBoris Brezillon 	pr_notice("ftl_cs: erase failed: err = %d\n", ret);
356884cfd90SBoris Brezillon     }
357884cfd90SBoris Brezillon 
3581da177e4SLinus Torvalds     kfree(erase);
3591da177e4SLinus Torvalds 
3601da177e4SLinus Torvalds     return ret;
3611da177e4SLinus Torvalds } /* erase_xfer */
3621da177e4SLinus Torvalds 
3631da177e4SLinus Torvalds /*======================================================================
3641da177e4SLinus Torvalds 
3651da177e4SLinus Torvalds     Prepare_xfer() takes a freshly erased transfer unit and gives
3661da177e4SLinus Torvalds     it an appropriate header.
3671da177e4SLinus Torvalds 
3681da177e4SLinus Torvalds ======================================================================*/
3691da177e4SLinus Torvalds 
prepare_xfer(partition_t * part,int i)3701da177e4SLinus Torvalds static int prepare_xfer(partition_t *part, int i)
3711da177e4SLinus Torvalds {
3721da177e4SLinus Torvalds     erase_unit_header_t header;
3731da177e4SLinus Torvalds     struct xfer_info_t *xfer;
3741da177e4SLinus Torvalds     int nbam, ret;
3753854be77SDavid Woodhouse     uint32_t ctl;
3761da177e4SLinus Torvalds     ssize_t retlen;
3771da177e4SLinus Torvalds     loff_t offset;
3781da177e4SLinus Torvalds 
3791da177e4SLinus Torvalds     xfer = &part->XferInfo[i];
3801da177e4SLinus Torvalds     xfer->state = XFER_FAILED;
3811da177e4SLinus Torvalds 
382289c0522SBrian Norris     pr_debug("ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset);
3831da177e4SLinus Torvalds 
3841da177e4SLinus Torvalds     /* Write the transfer unit header */
3851da177e4SLinus Torvalds     header = part->header;
3861da177e4SLinus Torvalds     header.LogicalEUN = cpu_to_le16(0xffff);
3871da177e4SLinus Torvalds     header.EraseCount = cpu_to_le32(xfer->EraseCount);
3881da177e4SLinus Torvalds 
389eda95cbfSArtem Bityutskiy     ret = mtd_write(part->mbd.mtd, xfer->Offset, sizeof(header), &retlen,
390eda95cbfSArtem Bityutskiy                     (u_char *)&header);
3911da177e4SLinus Torvalds 
3921da177e4SLinus Torvalds     if (ret) {
3931da177e4SLinus Torvalds 	return ret;
3941da177e4SLinus Torvalds     }
3951da177e4SLinus Torvalds 
3961da177e4SLinus Torvalds     /* Write the BAM stub */
397b756816dSArushi Singhal     nbam = DIV_ROUND_UP(part->BlocksPerUnit * sizeof(uint32_t) +
398b756816dSArushi Singhal 			le32_to_cpu(part->header.BAMOffset), SECTOR_SIZE);
3991da177e4SLinus Torvalds 
4001da177e4SLinus Torvalds     offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset);
4011da177e4SLinus Torvalds     ctl = cpu_to_le32(BLOCK_CONTROL);
4021da177e4SLinus Torvalds 
4033854be77SDavid Woodhouse     for (i = 0; i < nbam; i++, offset += sizeof(uint32_t)) {
4041da177e4SLinus Torvalds 
405eda95cbfSArtem Bityutskiy 	ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
406eda95cbfSArtem Bityutskiy                         (u_char *)&ctl);
4071da177e4SLinus Torvalds 
4081da177e4SLinus Torvalds 	if (ret)
4091da177e4SLinus Torvalds 	    return ret;
4101da177e4SLinus Torvalds     }
4111da177e4SLinus Torvalds     xfer->state = XFER_PREPARED;
4121da177e4SLinus Torvalds     return 0;
4131da177e4SLinus Torvalds 
4141da177e4SLinus Torvalds } /* prepare_xfer */
4151da177e4SLinus Torvalds 
4161da177e4SLinus Torvalds /*======================================================================
4171da177e4SLinus Torvalds 
4181da177e4SLinus Torvalds     Copy_erase_unit() takes a full erase block and a transfer unit,
4191da177e4SLinus Torvalds     copies everything to the transfer unit, then swaps the block
4201da177e4SLinus Torvalds     pointers.
4211da177e4SLinus Torvalds 
4221da177e4SLinus Torvalds     All data blocks are copied to the corresponding blocks in the
4231da177e4SLinus Torvalds     target unit, so the virtual block map does not need to be
4241da177e4SLinus Torvalds     updated.
4251da177e4SLinus Torvalds 
4261da177e4SLinus Torvalds ======================================================================*/
4271da177e4SLinus Torvalds 
copy_erase_unit(partition_t * part,uint16_t srcunit,uint16_t xferunit)4283854be77SDavid Woodhouse static int copy_erase_unit(partition_t *part, uint16_t srcunit,
4293854be77SDavid Woodhouse 			   uint16_t xferunit)
4301da177e4SLinus Torvalds {
4311da177e4SLinus Torvalds     u_char buf[SECTOR_SIZE];
4321da177e4SLinus Torvalds     struct eun_info_t *eun;
4331da177e4SLinus Torvalds     struct xfer_info_t *xfer;
4343854be77SDavid Woodhouse     uint32_t src, dest, free, i;
4353854be77SDavid Woodhouse     uint16_t unit;
4361da177e4SLinus Torvalds     int ret;
4371da177e4SLinus Torvalds     ssize_t retlen;
4381da177e4SLinus Torvalds     loff_t offset;
4393854be77SDavid Woodhouse     uint16_t srcunitswap = cpu_to_le16(srcunit);
4401da177e4SLinus Torvalds 
4411da177e4SLinus Torvalds     eun = &part->EUNInfo[srcunit];
4421da177e4SLinus Torvalds     xfer = &part->XferInfo[xferunit];
443289c0522SBrian Norris     pr_debug("ftl_cs: copying block 0x%x to 0x%x\n",
4441da177e4SLinus Torvalds 	  eun->Offset, xfer->Offset);
4451da177e4SLinus Torvalds 
4461da177e4SLinus Torvalds 
4471da177e4SLinus Torvalds     /* Read current BAM */
4481da177e4SLinus Torvalds     if (part->bam_index != srcunit) {
4491da177e4SLinus Torvalds 
4501da177e4SLinus Torvalds 	offset = eun->Offset + le32_to_cpu(part->header.BAMOffset);
4511da177e4SLinus Torvalds 
452329ad399SArtem Bityutskiy 	ret = mtd_read(part->mbd.mtd, offset,
453329ad399SArtem Bityutskiy                        part->BlocksPerUnit * sizeof(uint32_t), &retlen,
454329ad399SArtem Bityutskiy                        (u_char *)(part->bam_cache));
4551da177e4SLinus Torvalds 
4561da177e4SLinus Torvalds 	/* mark the cache bad, in case we get an error later */
4571da177e4SLinus Torvalds 	part->bam_index = 0xffff;
4581da177e4SLinus Torvalds 
4591da177e4SLinus Torvalds 	if (ret) {
4601da177e4SLinus Torvalds 	    printk( KERN_WARNING "ftl: Failed to read BAM cache in copy_erase_unit()!\n");
4611da177e4SLinus Torvalds 	    return ret;
4621da177e4SLinus Torvalds 	}
4631da177e4SLinus Torvalds     }
4641da177e4SLinus Torvalds 
4651da177e4SLinus Torvalds     /* Write the LogicalEUN for the transfer unit */
4661da177e4SLinus Torvalds     xfer->state = XFER_UNKNOWN;
4671da177e4SLinus Torvalds     offset = xfer->Offset + 20; /* Bad! */
4681da177e4SLinus Torvalds     unit = cpu_to_le16(0x7fff);
4691da177e4SLinus Torvalds 
470eda95cbfSArtem Bityutskiy     ret = mtd_write(part->mbd.mtd, offset, sizeof(uint16_t), &retlen,
471eda95cbfSArtem Bityutskiy                     (u_char *)&unit);
4721da177e4SLinus Torvalds 
4731da177e4SLinus Torvalds     if (ret) {
4741da177e4SLinus Torvalds 	printk( KERN_WARNING "ftl: Failed to write back to BAM cache in copy_erase_unit()!\n");
4751da177e4SLinus Torvalds 	return ret;
4761da177e4SLinus Torvalds     }
4771da177e4SLinus Torvalds 
4781da177e4SLinus Torvalds     /* Copy all data blocks from source unit to transfer unit */
4791da177e4SLinus Torvalds     src = eun->Offset; dest = xfer->Offset;
4801da177e4SLinus Torvalds 
4811da177e4SLinus Torvalds     free = 0;
4821da177e4SLinus Torvalds     ret = 0;
4831da177e4SLinus Torvalds     for (i = 0; i < part->BlocksPerUnit; i++) {
4841da177e4SLinus Torvalds 	switch (BLOCK_TYPE(le32_to_cpu(part->bam_cache[i]))) {
4851da177e4SLinus Torvalds 	case BLOCK_CONTROL:
4861da177e4SLinus Torvalds 	    /* This gets updated later */
4871da177e4SLinus Torvalds 	    break;
4881da177e4SLinus Torvalds 	case BLOCK_DATA:
4891da177e4SLinus Torvalds 	case BLOCK_REPLACEMENT:
490329ad399SArtem Bityutskiy 	    ret = mtd_read(part->mbd.mtd, src, SECTOR_SIZE, &retlen,
491329ad399SArtem Bityutskiy                            (u_char *)buf);
4921da177e4SLinus Torvalds 	    if (ret) {
4931da177e4SLinus Torvalds 		printk(KERN_WARNING "ftl: Error reading old xfer unit in copy_erase_unit\n");
4941da177e4SLinus Torvalds 		return ret;
4951da177e4SLinus Torvalds             }
4961da177e4SLinus Torvalds 
4971da177e4SLinus Torvalds 
498eda95cbfSArtem Bityutskiy 	    ret = mtd_write(part->mbd.mtd, dest, SECTOR_SIZE, &retlen,
499eda95cbfSArtem Bityutskiy                             (u_char *)buf);
5001da177e4SLinus Torvalds 	    if (ret)  {
5011da177e4SLinus Torvalds 		printk(KERN_WARNING "ftl: Error writing new xfer unit in copy_erase_unit\n");
5021da177e4SLinus Torvalds 		return ret;
5031da177e4SLinus Torvalds             }
5041da177e4SLinus Torvalds 
5051da177e4SLinus Torvalds 	    break;
5061da177e4SLinus Torvalds 	default:
5071da177e4SLinus Torvalds 	    /* All other blocks must be free */
5081da177e4SLinus Torvalds 	    part->bam_cache[i] = cpu_to_le32(0xffffffff);
5091da177e4SLinus Torvalds 	    free++;
5101da177e4SLinus Torvalds 	    break;
5111da177e4SLinus Torvalds 	}
5121da177e4SLinus Torvalds 	src += SECTOR_SIZE;
5131da177e4SLinus Torvalds 	dest += SECTOR_SIZE;
5141da177e4SLinus Torvalds     }
5151da177e4SLinus Torvalds 
5161da177e4SLinus Torvalds     /* Write the BAM to the transfer unit */
517eda95cbfSArtem Bityutskiy     ret = mtd_write(part->mbd.mtd,
518eda95cbfSArtem Bityutskiy                     xfer->Offset + le32_to_cpu(part->header.BAMOffset),
519eda95cbfSArtem Bityutskiy                     part->BlocksPerUnit * sizeof(int32_t),
520eda95cbfSArtem Bityutskiy                     &retlen,
5211da177e4SLinus Torvalds                     (u_char *)part->bam_cache);
5221da177e4SLinus Torvalds     if (ret) {
5231da177e4SLinus Torvalds 	printk( KERN_WARNING "ftl: Error writing BAM in copy_erase_unit\n");
5241da177e4SLinus Torvalds 	return ret;
5251da177e4SLinus Torvalds     }
5261da177e4SLinus Torvalds 
5271da177e4SLinus Torvalds 
5281da177e4SLinus Torvalds     /* All clear? Then update the LogicalEUN again */
529eda95cbfSArtem Bityutskiy     ret = mtd_write(part->mbd.mtd, xfer->Offset + 20, sizeof(uint16_t),
5301da177e4SLinus Torvalds                     &retlen, (u_char *)&srcunitswap);
5311da177e4SLinus Torvalds 
5321da177e4SLinus Torvalds     if (ret) {
5331da177e4SLinus Torvalds 	printk(KERN_WARNING "ftl: Error writing new LogicalEUN in copy_erase_unit\n");
5341da177e4SLinus Torvalds 	return ret;
5351da177e4SLinus Torvalds     }
5361da177e4SLinus Torvalds 
5371da177e4SLinus Torvalds 
5381da177e4SLinus Torvalds     /* Update the maps and usage stats*/
5396166a76fSFabian Frederick     swap(xfer->EraseCount, eun->EraseCount);
5406166a76fSFabian Frederick     swap(xfer->Offset, eun->Offset);
5411da177e4SLinus Torvalds     part->FreeTotal -= eun->Free;
5421da177e4SLinus Torvalds     part->FreeTotal += free;
5431da177e4SLinus Torvalds     eun->Free = free;
5441da177e4SLinus Torvalds     eun->Deleted = 0;
5451da177e4SLinus Torvalds 
5461da177e4SLinus Torvalds     /* Now, the cache should be valid for the new block */
5471da177e4SLinus Torvalds     part->bam_index = srcunit;
5481da177e4SLinus Torvalds 
5491da177e4SLinus Torvalds     return 0;
5501da177e4SLinus Torvalds } /* copy_erase_unit */
5511da177e4SLinus Torvalds 
5521da177e4SLinus Torvalds /*======================================================================
5531da177e4SLinus Torvalds 
5541da177e4SLinus Torvalds     reclaim_block() picks a full erase unit and a transfer unit and
5551da177e4SLinus Torvalds     then calls copy_erase_unit() to copy one to the other.  Then, it
5561da177e4SLinus Torvalds     schedules an erase on the expired block.
5571da177e4SLinus Torvalds 
5581da177e4SLinus Torvalds     What's a good way to decide which transfer unit and which erase
5591da177e4SLinus Torvalds     unit to use?  Beats me.  My way is to always pick the transfer
5601da177e4SLinus Torvalds     unit with the fewest erases, and usually pick the data unit with
5611da177e4SLinus Torvalds     the most deleted blocks.  But with a small probability, pick the
5621da177e4SLinus Torvalds     oldest data unit instead.  This means that we generally postpone
56392394b5cSBrian Norris     the next reclamation as long as possible, but shuffle static
5641da177e4SLinus Torvalds     stuff around a bit for wear leveling.
5651da177e4SLinus Torvalds 
5661da177e4SLinus Torvalds ======================================================================*/
5671da177e4SLinus Torvalds 
reclaim_block(partition_t * part)5681da177e4SLinus Torvalds static int reclaim_block(partition_t *part)
5691da177e4SLinus Torvalds {
5703854be77SDavid Woodhouse     uint16_t i, eun, xfer;
5713854be77SDavid Woodhouse     uint32_t best;
5721da177e4SLinus Torvalds     int queued, ret;
5731da177e4SLinus Torvalds 
574289c0522SBrian Norris     pr_debug("ftl_cs: reclaiming space...\n");
575289c0522SBrian Norris     pr_debug("NumTransferUnits == %x\n", part->header.NumTransferUnits);
5761da177e4SLinus Torvalds     /* Pick the least erased transfer unit */
5771da177e4SLinus Torvalds     best = 0xffffffff; xfer = 0xffff;
5781da177e4SLinus Torvalds     do {
5791da177e4SLinus Torvalds 	queued = 0;
5801da177e4SLinus Torvalds 	for (i = 0; i < part->header.NumTransferUnits; i++) {
5811da177e4SLinus Torvalds 	    int n=0;
5821da177e4SLinus Torvalds 	    if (part->XferInfo[i].state == XFER_UNKNOWN) {
583289c0522SBrian Norris 		pr_debug("XferInfo[%d].state == XFER_UNKNOWN\n",i);
5841da177e4SLinus Torvalds 		n=1;
5851da177e4SLinus Torvalds 		erase_xfer(part, i);
5861da177e4SLinus Torvalds 	    }
5871da177e4SLinus Torvalds 	    if (part->XferInfo[i].state == XFER_ERASING) {
588289c0522SBrian Norris 		pr_debug("XferInfo[%d].state == XFER_ERASING\n",i);
5891da177e4SLinus Torvalds 		n=1;
5901da177e4SLinus Torvalds 		queued = 1;
5911da177e4SLinus Torvalds 	    }
5921da177e4SLinus Torvalds 	    else if (part->XferInfo[i].state == XFER_ERASED) {
593289c0522SBrian Norris 		pr_debug("XferInfo[%d].state == XFER_ERASED\n",i);
5941da177e4SLinus Torvalds 		n=1;
5951da177e4SLinus Torvalds 		prepare_xfer(part, i);
5961da177e4SLinus Torvalds 	    }
5971da177e4SLinus Torvalds 	    if (part->XferInfo[i].state == XFER_PREPARED) {
598289c0522SBrian Norris 		pr_debug("XferInfo[%d].state == XFER_PREPARED\n",i);
5991da177e4SLinus Torvalds 		n=1;
6001da177e4SLinus Torvalds 		if (part->XferInfo[i].EraseCount <= best) {
6011da177e4SLinus Torvalds 		    best = part->XferInfo[i].EraseCount;
6021da177e4SLinus Torvalds 		    xfer = i;
6031da177e4SLinus Torvalds 		}
6041da177e4SLinus Torvalds 	    }
6051da177e4SLinus Torvalds 		if (!n)
606289c0522SBrian Norris 		    pr_debug("XferInfo[%d].state == %x\n",i, part->XferInfo[i].state);
6071da177e4SLinus Torvalds 
6081da177e4SLinus Torvalds 	}
6091da177e4SLinus Torvalds 	if (xfer == 0xffff) {
6101da177e4SLinus Torvalds 	    if (queued) {
611289c0522SBrian Norris 		pr_debug("ftl_cs: waiting for transfer "
6121da177e4SLinus Torvalds 		      "unit to be prepared...\n");
61385f2f2a8SArtem Bityutskiy 		mtd_sync(part->mbd.mtd);
6141da177e4SLinus Torvalds 	    } else {
6151da177e4SLinus Torvalds 		static int ne = 0;
6161da177e4SLinus Torvalds 		if (++ne < 5)
6171da177e4SLinus Torvalds 		    printk(KERN_NOTICE "ftl_cs: reclaim failed: no "
6181da177e4SLinus Torvalds 			   "suitable transfer units!\n");
6191da177e4SLinus Torvalds 		else
620289c0522SBrian Norris 		    pr_debug("ftl_cs: reclaim failed: no "
6211da177e4SLinus Torvalds 			  "suitable transfer units!\n");
6221da177e4SLinus Torvalds 
6231da177e4SLinus Torvalds 		return -EIO;
6241da177e4SLinus Torvalds 	    }
6251da177e4SLinus Torvalds 	}
6261da177e4SLinus Torvalds     } while (xfer == 0xffff);
6271da177e4SLinus Torvalds 
6281da177e4SLinus Torvalds     eun = 0;
6291da177e4SLinus Torvalds     if ((jiffies % shuffle_freq) == 0) {
630289c0522SBrian Norris 	pr_debug("ftl_cs: recycling freshest block...\n");
6311da177e4SLinus Torvalds 	best = 0xffffffff;
6321da177e4SLinus Torvalds 	for (i = 0; i < part->DataUnits; i++)
6331da177e4SLinus Torvalds 	    if (part->EUNInfo[i].EraseCount <= best) {
6341da177e4SLinus Torvalds 		best = part->EUNInfo[i].EraseCount;
6351da177e4SLinus Torvalds 		eun = i;
6361da177e4SLinus Torvalds 	    }
6371da177e4SLinus Torvalds     } else {
6381da177e4SLinus Torvalds 	best = 0;
6391da177e4SLinus Torvalds 	for (i = 0; i < part->DataUnits; i++)
6401da177e4SLinus Torvalds 	    if (part->EUNInfo[i].Deleted >= best) {
6411da177e4SLinus Torvalds 		best = part->EUNInfo[i].Deleted;
6421da177e4SLinus Torvalds 		eun = i;
6431da177e4SLinus Torvalds 	    }
6441da177e4SLinus Torvalds 	if (best == 0) {
6451da177e4SLinus Torvalds 	    static int ne = 0;
6461da177e4SLinus Torvalds 	    if (++ne < 5)
6471da177e4SLinus Torvalds 		printk(KERN_NOTICE "ftl_cs: reclaim failed: "
6481da177e4SLinus Torvalds 		       "no free blocks!\n");
6491da177e4SLinus Torvalds 	    else
650289c0522SBrian Norris 		pr_debug("ftl_cs: reclaim failed: "
6511da177e4SLinus Torvalds 		       "no free blocks!\n");
6521da177e4SLinus Torvalds 
6531da177e4SLinus Torvalds 	    return -EIO;
6541da177e4SLinus Torvalds 	}
6551da177e4SLinus Torvalds     }
6561da177e4SLinus Torvalds     ret = copy_erase_unit(part, eun, xfer);
6571da177e4SLinus Torvalds     if (!ret)
6581da177e4SLinus Torvalds 	erase_xfer(part, xfer);
6591da177e4SLinus Torvalds     else
6601da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: copy_erase_unit failed!\n");
6611da177e4SLinus Torvalds     return ret;
6621da177e4SLinus Torvalds } /* reclaim_block */
6631da177e4SLinus Torvalds 
6641da177e4SLinus Torvalds /*======================================================================
6651da177e4SLinus Torvalds 
6661da177e4SLinus Torvalds     Find_free() searches for a free block.  If necessary, it updates
6671da177e4SLinus Torvalds     the BAM cache for the erase unit containing the free block.  It
6681da177e4SLinus Torvalds     returns the block index -- the erase unit is just the currently
6691da177e4SLinus Torvalds     cached unit.  If there are no free blocks, it returns 0 -- this
6701da177e4SLinus Torvalds     is never a valid data block because it contains the header.
6711da177e4SLinus Torvalds 
6721da177e4SLinus Torvalds ======================================================================*/
6731da177e4SLinus Torvalds 
6741da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
dump_lists(partition_t * part)6751da177e4SLinus Torvalds static void dump_lists(partition_t *part)
6761da177e4SLinus Torvalds {
6771da177e4SLinus Torvalds     int i;
6781da177e4SLinus Torvalds     printk(KERN_DEBUG "ftl_cs: Free total = %d\n", part->FreeTotal);
6791da177e4SLinus Torvalds     for (i = 0; i < part->DataUnits; i++)
6801da177e4SLinus Torvalds 	printk(KERN_DEBUG "ftl_cs:   unit %d: %d phys, %d free, "
6811da177e4SLinus Torvalds 	       "%d deleted\n", i,
6821da177e4SLinus Torvalds 	       part->EUNInfo[i].Offset >> part->header.EraseUnitSize,
6831da177e4SLinus Torvalds 	       part->EUNInfo[i].Free, part->EUNInfo[i].Deleted);
6841da177e4SLinus Torvalds }
6851da177e4SLinus Torvalds #endif
6861da177e4SLinus Torvalds 
find_free(partition_t * part)6873854be77SDavid Woodhouse static uint32_t find_free(partition_t *part)
6881da177e4SLinus Torvalds {
6893854be77SDavid Woodhouse     uint16_t stop, eun;
6903854be77SDavid Woodhouse     uint32_t blk;
6911da177e4SLinus Torvalds     size_t retlen;
6921da177e4SLinus Torvalds     int ret;
6931da177e4SLinus Torvalds 
6941da177e4SLinus Torvalds     /* Find an erase unit with some free space */
6951da177e4SLinus Torvalds     stop = (part->bam_index == 0xffff) ? 0 : part->bam_index;
6961da177e4SLinus Torvalds     eun = stop;
6971da177e4SLinus Torvalds     do {
6981da177e4SLinus Torvalds 	if (part->EUNInfo[eun].Free != 0) break;
6991da177e4SLinus Torvalds 	/* Wrap around at end of table */
7001da177e4SLinus Torvalds 	if (++eun == part->DataUnits) eun = 0;
7011da177e4SLinus Torvalds     } while (eun != stop);
7021da177e4SLinus Torvalds 
7031da177e4SLinus Torvalds     if (part->EUNInfo[eun].Free == 0)
7041da177e4SLinus Torvalds 	return 0;
7051da177e4SLinus Torvalds 
7061da177e4SLinus Torvalds     /* Is this unit's BAM cached? */
7071da177e4SLinus Torvalds     if (eun != part->bam_index) {
7081da177e4SLinus Torvalds 	/* Invalidate cache */
7091da177e4SLinus Torvalds 	part->bam_index = 0xffff;
7101da177e4SLinus Torvalds 
711329ad399SArtem Bityutskiy 	ret = mtd_read(part->mbd.mtd,
7121da177e4SLinus Torvalds                        part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset),
7133854be77SDavid Woodhouse                        part->BlocksPerUnit * sizeof(uint32_t),
714329ad399SArtem Bityutskiy                        &retlen,
715329ad399SArtem Bityutskiy                        (u_char *)(part->bam_cache));
7161da177e4SLinus Torvalds 
7171da177e4SLinus Torvalds 	if (ret) {
7181da177e4SLinus Torvalds 	    printk(KERN_WARNING"ftl: Error reading BAM in find_free\n");
7191da177e4SLinus Torvalds 	    return 0;
7201da177e4SLinus Torvalds 	}
7211da177e4SLinus Torvalds 	part->bam_index = eun;
7221da177e4SLinus Torvalds     }
7231da177e4SLinus Torvalds 
7241da177e4SLinus Torvalds     /* Find a free block */
7251da177e4SLinus Torvalds     for (blk = 0; blk < part->BlocksPerUnit; blk++)
7261da177e4SLinus Torvalds 	if (BLOCK_FREE(le32_to_cpu(part->bam_cache[blk]))) break;
7271da177e4SLinus Torvalds     if (blk == part->BlocksPerUnit) {
7281da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
7291da177e4SLinus Torvalds 	static int ne = 0;
7301da177e4SLinus Torvalds 	if (++ne == 1)
7311da177e4SLinus Torvalds 	    dump_lists(part);
7321da177e4SLinus Torvalds #endif
7331da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: bad free list!\n");
7341da177e4SLinus Torvalds 	return 0;
7351da177e4SLinus Torvalds     }
736289c0522SBrian Norris     pr_debug("ftl_cs: found free block at %d in %d\n", blk, eun);
7371da177e4SLinus Torvalds     return blk;
7381da177e4SLinus Torvalds 
7391da177e4SLinus Torvalds } /* find_free */
7401da177e4SLinus Torvalds 
7411da177e4SLinus Torvalds 
7421da177e4SLinus Torvalds /*======================================================================
7431da177e4SLinus Torvalds 
7441da177e4SLinus Torvalds     Read a series of sectors from an FTL partition.
7451da177e4SLinus Torvalds 
7461da177e4SLinus Torvalds ======================================================================*/
7471da177e4SLinus Torvalds 
ftl_read(partition_t * part,caddr_t buffer,u_long sector,u_long nblocks)7481da177e4SLinus Torvalds static int ftl_read(partition_t *part, caddr_t buffer,
7491da177e4SLinus Torvalds 		    u_long sector, u_long nblocks)
7501da177e4SLinus Torvalds {
7513854be77SDavid Woodhouse     uint32_t log_addr, bsize;
7521da177e4SLinus Torvalds     u_long i;
7531da177e4SLinus Torvalds     int ret;
7541da177e4SLinus Torvalds     size_t offset, retlen;
7551da177e4SLinus Torvalds 
756289c0522SBrian Norris     pr_debug("ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n",
7571da177e4SLinus Torvalds 	  part, sector, nblocks);
7581da177e4SLinus Torvalds     if (!(part->state & FTL_FORMATTED)) {
7591da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: bad partition\n");
7601da177e4SLinus Torvalds 	return -EIO;
7611da177e4SLinus Torvalds     }
7621da177e4SLinus Torvalds     bsize = 1 << part->header.EraseUnitSize;
7631da177e4SLinus Torvalds 
7641da177e4SLinus Torvalds     for (i = 0; i < nblocks; i++) {
7651da177e4SLinus Torvalds 	if (((sector+i) * SECTOR_SIZE) >= le32_to_cpu(part->header.FormattedSize)) {
7661da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs: bad read offset\n");
7671da177e4SLinus Torvalds 	    return -EIO;
7681da177e4SLinus Torvalds 	}
7691da177e4SLinus Torvalds 	log_addr = part->VirtualBlockMap[sector+i];
7701da177e4SLinus Torvalds 	if (log_addr == 0xffffffff)
7711da177e4SLinus Torvalds 	    memset(buffer, 0, SECTOR_SIZE);
7721da177e4SLinus Torvalds 	else {
7731da177e4SLinus Torvalds 	    offset = (part->EUNInfo[log_addr / bsize].Offset
7741da177e4SLinus Torvalds 			  + (log_addr % bsize));
775329ad399SArtem Bityutskiy 	    ret = mtd_read(part->mbd.mtd, offset, SECTOR_SIZE, &retlen,
776329ad399SArtem Bityutskiy                            (u_char *)buffer);
7771da177e4SLinus Torvalds 
7781da177e4SLinus Torvalds 	    if (ret) {
7791da177e4SLinus Torvalds 		printk(KERN_WARNING "Error reading MTD device in ftl_read()\n");
7801da177e4SLinus Torvalds 		return ret;
7811da177e4SLinus Torvalds 	    }
7821da177e4SLinus Torvalds 	}
7831da177e4SLinus Torvalds 	buffer += SECTOR_SIZE;
7841da177e4SLinus Torvalds     }
7851da177e4SLinus Torvalds     return 0;
7861da177e4SLinus Torvalds } /* ftl_read */
7871da177e4SLinus Torvalds 
7881da177e4SLinus Torvalds /*======================================================================
7891da177e4SLinus Torvalds 
7901da177e4SLinus Torvalds     Write a series of sectors to an FTL partition
7911da177e4SLinus Torvalds 
7921da177e4SLinus Torvalds ======================================================================*/
7931da177e4SLinus Torvalds 
set_bam_entry(partition_t * part,uint32_t log_addr,uint32_t virt_addr)7943854be77SDavid Woodhouse static int set_bam_entry(partition_t *part, uint32_t log_addr,
7953854be77SDavid Woodhouse 			 uint32_t virt_addr)
7961da177e4SLinus Torvalds {
7973854be77SDavid Woodhouse     uint32_t bsize, blk, le_virt_addr;
7981da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
7993854be77SDavid Woodhouse     uint32_t old_addr;
8001da177e4SLinus Torvalds #endif
8013854be77SDavid Woodhouse     uint16_t eun;
8021da177e4SLinus Torvalds     int ret;
8031da177e4SLinus Torvalds     size_t retlen, offset;
8041da177e4SLinus Torvalds 
805289c0522SBrian Norris     pr_debug("ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n",
8061da177e4SLinus Torvalds 	  part, log_addr, virt_addr);
8071da177e4SLinus Torvalds     bsize = 1 << part->header.EraseUnitSize;
8081da177e4SLinus Torvalds     eun = log_addr / bsize;
8091da177e4SLinus Torvalds     blk = (log_addr % bsize) / SECTOR_SIZE;
8103854be77SDavid Woodhouse     offset = (part->EUNInfo[eun].Offset + blk * sizeof(uint32_t) +
8111da177e4SLinus Torvalds 		  le32_to_cpu(part->header.BAMOffset));
8121da177e4SLinus Torvalds 
8131da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
814329ad399SArtem Bityutskiy     ret = mtd_read(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
815329ad399SArtem Bityutskiy                    (u_char *)&old_addr);
8161da177e4SLinus Torvalds     if (ret) {
8171da177e4SLinus Torvalds 	printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret);
8181da177e4SLinus Torvalds 	return ret;
8191da177e4SLinus Torvalds     }
8201da177e4SLinus Torvalds     old_addr = le32_to_cpu(old_addr);
8211da177e4SLinus Torvalds 
8221da177e4SLinus Torvalds     if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) ||
8231da177e4SLinus Torvalds 	((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) ||
8241da177e4SLinus Torvalds 	(!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) {
8251da177e4SLinus Torvalds 	static int ne = 0;
8261da177e4SLinus Torvalds 	if (++ne < 5) {
8271da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs: set_bam_entry() inconsistency!\n");
8281da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs:   log_addr = 0x%x, old = 0x%x"
8291da177e4SLinus Torvalds 		   ", new = 0x%x\n", log_addr, old_addr, virt_addr);
8301da177e4SLinus Torvalds 	}
8311da177e4SLinus Torvalds 	return -EIO;
8321da177e4SLinus Torvalds     }
8331da177e4SLinus Torvalds #endif
8341da177e4SLinus Torvalds     le_virt_addr = cpu_to_le32(virt_addr);
8351da177e4SLinus Torvalds     if (part->bam_index == eun) {
8361da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
8371da177e4SLinus Torvalds 	if (le32_to_cpu(part->bam_cache[blk]) != old_addr) {
8381da177e4SLinus Torvalds 	    static int ne = 0;
8391da177e4SLinus Torvalds 	    if (++ne < 5) {
8401da177e4SLinus Torvalds 		printk(KERN_NOTICE "ftl_cs: set_bam_entry() "
8411da177e4SLinus Torvalds 		       "inconsistency!\n");
8421da177e4SLinus Torvalds 		printk(KERN_NOTICE "ftl_cs:   log_addr = 0x%x, cache"
8431da177e4SLinus Torvalds 		       " = 0x%x\n",
8441da177e4SLinus Torvalds 		       le32_to_cpu(part->bam_cache[blk]), old_addr);
8451da177e4SLinus Torvalds 	    }
8461da177e4SLinus Torvalds 	    return -EIO;
8471da177e4SLinus Torvalds 	}
8481da177e4SLinus Torvalds #endif
8491da177e4SLinus Torvalds 	part->bam_cache[blk] = le_virt_addr;
8501da177e4SLinus Torvalds     }
851eda95cbfSArtem Bityutskiy     ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
852eda95cbfSArtem Bityutskiy                     (u_char *)&le_virt_addr);
8531da177e4SLinus Torvalds 
8541da177e4SLinus Torvalds     if (ret) {
8551da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n");
8561da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs:   log_addr = 0x%x, new = 0x%x\n",
8571da177e4SLinus Torvalds 	       log_addr, virt_addr);
8581da177e4SLinus Torvalds     }
8591da177e4SLinus Torvalds     return ret;
8601da177e4SLinus Torvalds } /* set_bam_entry */
8611da177e4SLinus Torvalds 
ftl_write(partition_t * part,caddr_t buffer,u_long sector,u_long nblocks)8621da177e4SLinus Torvalds static int ftl_write(partition_t *part, caddr_t buffer,
8631da177e4SLinus Torvalds 		     u_long sector, u_long nblocks)
8641da177e4SLinus Torvalds {
8653854be77SDavid Woodhouse     uint32_t bsize, log_addr, virt_addr, old_addr, blk;
8661da177e4SLinus Torvalds     u_long i;
8671da177e4SLinus Torvalds     int ret;
8681da177e4SLinus Torvalds     size_t retlen, offset;
8691da177e4SLinus Torvalds 
870289c0522SBrian Norris     pr_debug("ftl_cs: ftl_write(0x%p, %ld, %ld)\n",
8711da177e4SLinus Torvalds 	  part, sector, nblocks);
8721da177e4SLinus Torvalds     if (!(part->state & FTL_FORMATTED)) {
8731da177e4SLinus Torvalds 	printk(KERN_NOTICE "ftl_cs: bad partition\n");
8741da177e4SLinus Torvalds 	return -EIO;
8751da177e4SLinus Torvalds     }
8761da177e4SLinus Torvalds     /* See if we need to reclaim space, before we start */
8771da177e4SLinus Torvalds     while (part->FreeTotal < nblocks) {
8781da177e4SLinus Torvalds 	ret = reclaim_block(part);
8791da177e4SLinus Torvalds 	if (ret)
8801da177e4SLinus Torvalds 	    return ret;
8811da177e4SLinus Torvalds     }
8821da177e4SLinus Torvalds 
8831da177e4SLinus Torvalds     bsize = 1 << part->header.EraseUnitSize;
8841da177e4SLinus Torvalds 
8851da177e4SLinus Torvalds     virt_addr = sector * SECTOR_SIZE | BLOCK_DATA;
8861da177e4SLinus Torvalds     for (i = 0; i < nblocks; i++) {
8871da177e4SLinus Torvalds 	if (virt_addr >= le32_to_cpu(part->header.FormattedSize)) {
8881da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs: bad write offset\n");
8891da177e4SLinus Torvalds 	    return -EIO;
8901da177e4SLinus Torvalds 	}
8911da177e4SLinus Torvalds 
8921da177e4SLinus Torvalds 	/* Grab a free block */
8931da177e4SLinus Torvalds 	blk = find_free(part);
8941da177e4SLinus Torvalds 	if (blk == 0) {
8951da177e4SLinus Torvalds 	    static int ne = 0;
8961da177e4SLinus Torvalds 	    if (++ne < 5)
8971da177e4SLinus Torvalds 		printk(KERN_NOTICE "ftl_cs: internal error: "
8981da177e4SLinus Torvalds 		       "no free blocks!\n");
8991da177e4SLinus Torvalds 	    return -ENOSPC;
9001da177e4SLinus Torvalds 	}
9011da177e4SLinus Torvalds 
9021da177e4SLinus Torvalds 	/* Tag the BAM entry, and write the new block */
9031da177e4SLinus Torvalds 	log_addr = part->bam_index * bsize + blk * SECTOR_SIZE;
9041da177e4SLinus Torvalds 	part->EUNInfo[part->bam_index].Free--;
9051da177e4SLinus Torvalds 	part->FreeTotal--;
9061da177e4SLinus Torvalds 	if (set_bam_entry(part, log_addr, 0xfffffffe))
9071da177e4SLinus Torvalds 	    return -EIO;
9081da177e4SLinus Torvalds 	part->EUNInfo[part->bam_index].Deleted++;
9091da177e4SLinus Torvalds 	offset = (part->EUNInfo[part->bam_index].Offset +
9101da177e4SLinus Torvalds 		      blk * SECTOR_SIZE);
911eda95cbfSArtem Bityutskiy 	ret = mtd_write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen, buffer);
9121da177e4SLinus Torvalds 
9131da177e4SLinus Torvalds 	if (ret) {
9141da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs: block write failed!\n");
9151da177e4SLinus Torvalds 	    printk(KERN_NOTICE "ftl_cs:   log_addr = 0x%x, virt_addr"
9161da177e4SLinus Torvalds 		   " = 0x%x, Offset = 0x%zx\n", log_addr, virt_addr,
9171da177e4SLinus Torvalds 		   offset);
9181da177e4SLinus Torvalds 	    return -EIO;
9191da177e4SLinus Torvalds 	}
9201da177e4SLinus Torvalds 
9211da177e4SLinus Torvalds 	/* Only delete the old entry when the new entry is ready */
9221da177e4SLinus Torvalds 	old_addr = part->VirtualBlockMap[sector+i];
9231da177e4SLinus Torvalds 	if (old_addr != 0xffffffff) {
9241da177e4SLinus Torvalds 	    part->VirtualBlockMap[sector+i] = 0xffffffff;
9251da177e4SLinus Torvalds 	    part->EUNInfo[old_addr/bsize].Deleted++;
9261da177e4SLinus Torvalds 	    if (set_bam_entry(part, old_addr, 0))
9271da177e4SLinus Torvalds 		return -EIO;
9281da177e4SLinus Torvalds 	}
9291da177e4SLinus Torvalds 
9301da177e4SLinus Torvalds 	/* Finally, set up the new pointers */
9311da177e4SLinus Torvalds 	if (set_bam_entry(part, log_addr, virt_addr))
9321da177e4SLinus Torvalds 	    return -EIO;
9331da177e4SLinus Torvalds 	part->VirtualBlockMap[sector+i] = log_addr;
9341da177e4SLinus Torvalds 	part->EUNInfo[part->bam_index].Deleted--;
9351da177e4SLinus Torvalds 
9361da177e4SLinus Torvalds 	buffer += SECTOR_SIZE;
9371da177e4SLinus Torvalds 	virt_addr += SECTOR_SIZE;
9381da177e4SLinus Torvalds     }
9391da177e4SLinus Torvalds     return 0;
9401da177e4SLinus Torvalds } /* ftl_write */
9411da177e4SLinus Torvalds 
ftl_getgeo(struct mtd_blktrans_dev * dev,struct hd_geometry * geo)9421da177e4SLinus Torvalds static int ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
9431da177e4SLinus Torvalds {
944*bf3e6b8fSGaosheng Cui 	partition_t *part = container_of(dev, struct partition_t, mbd);
9451da177e4SLinus Torvalds 	u_long sect;
9461da177e4SLinus Torvalds 
9471da177e4SLinus Torvalds 	/* Sort of arbitrary: round size down to 4KiB boundary */
9481da177e4SLinus Torvalds 	sect = le32_to_cpu(part->header.FormattedSize)/SECTOR_SIZE;
9491da177e4SLinus Torvalds 
9501da177e4SLinus Torvalds 	geo->heads = 1;
9511da177e4SLinus Torvalds 	geo->sectors = 8;
9521da177e4SLinus Torvalds 	geo->cylinders = sect >> 3;
9531da177e4SLinus Torvalds 
9541da177e4SLinus Torvalds 	return 0;
9551da177e4SLinus Torvalds }
9561da177e4SLinus Torvalds 
ftl_readsect(struct mtd_blktrans_dev * dev,unsigned long block,char * buf)9571da177e4SLinus Torvalds static int ftl_readsect(struct mtd_blktrans_dev *dev,
9581da177e4SLinus Torvalds 			      unsigned long block, char *buf)
9591da177e4SLinus Torvalds {
9601da177e4SLinus Torvalds 	return ftl_read((void *)dev, buf, block, 1);
9611da177e4SLinus Torvalds }
9621da177e4SLinus Torvalds 
ftl_writesect(struct mtd_blktrans_dev * dev,unsigned long block,char * buf)9631da177e4SLinus Torvalds static int ftl_writesect(struct mtd_blktrans_dev *dev,
9641da177e4SLinus Torvalds 			      unsigned long block, char *buf)
9651da177e4SLinus Torvalds {
9661da177e4SLinus Torvalds 	return ftl_write((void *)dev, buf, block, 1);
9671da177e4SLinus Torvalds }
9681da177e4SLinus Torvalds 
ftl_discardsect(struct mtd_blktrans_dev * dev,unsigned long sector,unsigned nr_sects)969fdc53971SDavid Woodhouse static int ftl_discardsect(struct mtd_blktrans_dev *dev,
970fdc53971SDavid Woodhouse 			   unsigned long sector, unsigned nr_sects)
971fdc53971SDavid Woodhouse {
972*bf3e6b8fSGaosheng Cui 	partition_t *part = container_of(dev, struct partition_t, mbd);
973fdc53971SDavid Woodhouse 	uint32_t bsize = 1 << part->header.EraseUnitSize;
974fdc53971SDavid Woodhouse 
975289c0522SBrian Norris 	pr_debug("FTL erase sector %ld for %d sectors\n",
976fdc53971SDavid Woodhouse 	      sector, nr_sects);
977fdc53971SDavid Woodhouse 
978fdc53971SDavid Woodhouse 	while (nr_sects) {
979fdc53971SDavid Woodhouse 		uint32_t old_addr = part->VirtualBlockMap[sector];
980fdc53971SDavid Woodhouse 		if (old_addr != 0xffffffff) {
981fdc53971SDavid Woodhouse 			part->VirtualBlockMap[sector] = 0xffffffff;
982fdc53971SDavid Woodhouse 			part->EUNInfo[old_addr/bsize].Deleted++;
983fdc53971SDavid Woodhouse 			if (set_bam_entry(part, old_addr, 0))
984fdc53971SDavid Woodhouse 				return -EIO;
985fdc53971SDavid Woodhouse 		}
986fdc53971SDavid Woodhouse 		nr_sects--;
987fdc53971SDavid Woodhouse 		sector++;
988fdc53971SDavid Woodhouse 	}
989fdc53971SDavid Woodhouse 
990fdc53971SDavid Woodhouse 	return 0;
991fdc53971SDavid Woodhouse }
9921da177e4SLinus Torvalds /*====================================================================*/
9931da177e4SLinus Torvalds 
ftl_freepart(partition_t * part)9945ce45d50SAdrian Bunk static void ftl_freepart(partition_t *part)
9951da177e4SLinus Torvalds {
9961da177e4SLinus Torvalds 	vfree(part->VirtualBlockMap);
9971da177e4SLinus Torvalds 	part->VirtualBlockMap = NULL;
9981da177e4SLinus Torvalds 	kfree(part->EUNInfo);
9991da177e4SLinus Torvalds 	part->EUNInfo = NULL;
10001da177e4SLinus Torvalds 	kfree(part->XferInfo);
10011da177e4SLinus Torvalds 	part->XferInfo = NULL;
10021da177e4SLinus Torvalds 	kfree(part->bam_cache);
10031da177e4SLinus Torvalds 	part->bam_cache = NULL;
10041da177e4SLinus Torvalds } /* ftl_freepart */
10051da177e4SLinus Torvalds 
ftl_add_mtd(struct mtd_blktrans_ops * tr,struct mtd_info * mtd)10061da177e4SLinus Torvalds static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
10071da177e4SLinus Torvalds {
10081da177e4SLinus Torvalds 	partition_t *partition;
10091da177e4SLinus Torvalds 
101095b93a0cSBurman Yan 	partition = kzalloc(sizeof(partition_t), GFP_KERNEL);
10111da177e4SLinus Torvalds 
10121da177e4SLinus Torvalds 	if (!partition) {
10131da177e4SLinus Torvalds 		printk(KERN_WARNING "No memory to scan for FTL on %s\n",
10141da177e4SLinus Torvalds 		       mtd->name);
10151da177e4SLinus Torvalds 		return;
10161da177e4SLinus Torvalds 	}
10171da177e4SLinus Torvalds 
10181da177e4SLinus Torvalds 	partition->mbd.mtd = mtd;
10191da177e4SLinus Torvalds 
10201da177e4SLinus Torvalds 	if ((scan_header(partition) == 0) &&
10211da177e4SLinus Torvalds 	    (build_maps(partition) == 0)) {
10221da177e4SLinus Torvalds 
10231da177e4SLinus Torvalds 		partition->state = FTL_FORMATTED;
10241da177e4SLinus Torvalds #ifdef PCMCIA_DEBUG
10251da177e4SLinus Torvalds 		printk(KERN_INFO "ftl_cs: opening %d KiB FTL partition\n",
10261da177e4SLinus Torvalds 		       le32_to_cpu(partition->header.FormattedSize) >> 10);
10271da177e4SLinus Torvalds #endif
10281da177e4SLinus Torvalds 		partition->mbd.size = le32_to_cpu(partition->header.FormattedSize) >> 9;
102919187672SRichard Purdie 
10301da177e4SLinus Torvalds 		partition->mbd.tr = tr;
10311da177e4SLinus Torvalds 		partition->mbd.devnum = -1;
1032ffd18c97SChristoph Hellwig 		if (!add_mtd_blktrans_dev(&partition->mbd))
10331da177e4SLinus Torvalds 			return;
10341da177e4SLinus Torvalds 	}
10351da177e4SLinus Torvalds 
10361da177e4SLinus Torvalds 	kfree(partition);
10371da177e4SLinus Torvalds }
10381da177e4SLinus Torvalds 
ftl_remove_dev(struct mtd_blktrans_dev * dev)10391da177e4SLinus Torvalds static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
10401da177e4SLinus Torvalds {
10411da177e4SLinus Torvalds 	del_mtd_blktrans_dev(dev);
10421da177e4SLinus Torvalds 	ftl_freepart((partition_t *)dev);
10431da177e4SLinus Torvalds }
10441da177e4SLinus Torvalds 
10455ce45d50SAdrian Bunk static struct mtd_blktrans_ops ftl_tr = {
10461da177e4SLinus Torvalds 	.name		= "ftl",
10471da177e4SLinus Torvalds 	.major		= FTL_MAJOR,
10481da177e4SLinus Torvalds 	.part_bits	= PART_BITS,
104919187672SRichard Purdie 	.blksize 	= SECTOR_SIZE,
10501da177e4SLinus Torvalds 	.readsect	= ftl_readsect,
10511da177e4SLinus Torvalds 	.writesect	= ftl_writesect,
1052fdc53971SDavid Woodhouse 	.discard	= ftl_discardsect,
10531da177e4SLinus Torvalds 	.getgeo		= ftl_getgeo,
10541da177e4SLinus Torvalds 	.add_mtd	= ftl_add_mtd,
10551da177e4SLinus Torvalds 	.remove_dev	= ftl_remove_dev,
10561da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
10571da177e4SLinus Torvalds };
10581da177e4SLinus Torvalds 
1059c45f0739SDejin Zheng module_mtd_blktrans(ftl_tr);
10601da177e4SLinus Torvalds 
10611da177e4SLinus Torvalds MODULE_LICENSE("Dual MPL/GPL");
10621da177e4SLinus Torvalds MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
10631da177e4SLinus Torvalds MODULE_DESCRIPTION("Support code for Flash Translation Layer, used on PCMCIA devices");
1064