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