blob: 12fd7ebd3fd876e2711c61ef8bec22ac2e6cb133 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/* This version ported to the Linux-MTD system by dwmw2@infradead.org
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 *
3 * Fixes: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
4 * - fixes some leaks on failure in build_maps and ftl_notify_add, cleanups
5 *
6 * Based on:
7 */
8/*======================================================================
9
10 A Flash Translation Layer memory card driver
11
12 This driver implements a disk-like block device driver with an
13 apparent block size of 512 bytes for flash memory cards.
14
15 ftl_cs.c 1.62 2000/02/01 00:59:04
16
17 The contents of this file are subject to the Mozilla Public
18 License Version 1.1 (the "License"); you may not use this file
19 except in compliance with the License. You may obtain a copy of
20 the License at http://www.mozilla.org/MPL/
21
22 Software distributed under the License is distributed on an "AS
23 IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
24 implied. See the License for the specific language governing
25 rights and limitations under the License.
26
27 The initial developer of the original code is David A. Hinds
28 <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
David Woodhousea1452a32010-08-08 20:58:20 +010029 are Copyright © 1999 David A. Hinds. All Rights Reserved.
Linus Torvalds1da177e2005-04-16 15:20:36 -070030
31 Alternatively, the contents of this file may be used under the
32 terms of the GNU General Public License version 2 (the "GPL"), in
33 which case the provisions of the GPL are applicable instead of the
34 above. If you wish to allow the use of your version of this file
35 only under the terms of the GPL and not to allow others to use
36 your version of this file under the MPL, indicate your decision
37 by deleting the provisions above and replace them with the notice
38 and other provisions required by the GPL. If you do not delete
39 the provisions above, a recipient may use your version of this
40 file under either the MPL or the GPL.
41
42 LEGAL NOTE: The FTL format is patented by M-Systems. They have
43 granted a license for its use with PCMCIA devices:
44
45 "M-Systems grants a royalty-free, non-exclusive license under
46 any presently existing M-Systems intellectual property rights
47 necessary for the design and development of FTL-compatible
48 drivers, file systems and utilities using the data formats with
49 PCMCIA PC Cards as described in the PCMCIA Flash Translation
50 Layer (FTL) Specification."
51
52 Use of the FTL format for non-PCMCIA applications may be an
53 infringement of these patents. For additional information,
Justin P. Mattock631dd1a2010-10-18 11:03:14 +020054 contact M-Systems directly. M-Systems since acquired by Sandisk.
Thomas Gleixner97894cd2005-11-07 11:15:26 +000055
Linus Torvalds1da177e2005-04-16 15:20:36 -070056======================================================================*/
57#include <linux/mtd/blktrans.h>
58#include <linux/module.h>
59#include <linux/mtd/mtd.h>
60/*#define PSYCHO_DEBUG */
61
62#include <linux/kernel.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070063#include <linux/ptrace.h>
64#include <linux/slab.h>
65#include <linux/string.h>
66#include <linux/timer.h>
67#include <linux/major.h>
68#include <linux/fs.h>
69#include <linux/init.h>
70#include <linux/hdreg.h>
71#include <linux/vmalloc.h>
72#include <linux/blkpg.h>
73#include <asm/uaccess.h>
74
75#include <linux/mtd/ftl.h>
76
77/*====================================================================*/
78
79/* Parameters that can be set with 'insmod' */
80static int shuffle_freq = 50;
81module_param(shuffle_freq, int, 0);
82
83/*====================================================================*/
84
85/* Major device # for FTL device */
86#ifndef FTL_MAJOR
87#define FTL_MAJOR 44
88#endif
89
90
91/*====================================================================*/
92
93/* Maximum number of separate memory devices we'll allow */
94#define MAX_DEV 4
95
96/* Maximum number of regions per device */
97#define MAX_REGION 4
98
99/* Maximum number of partitions in an FTL region */
100#define PART_BITS 4
101
102/* Maximum number of outstanding erase requests per socket */
103#define MAX_ERASE 8
104
105/* Sector size -- shouldn't need to change */
106#define SECTOR_SIZE 512
107
108
109/* Each memory region corresponds to a minor device */
110typedef struct partition_t {
111 struct mtd_blktrans_dev mbd;
David Woodhouse3854be72008-12-10 14:06:42 +0000112 uint32_t state;
113 uint32_t *VirtualBlockMap;
114 uint32_t *VirtualPageMap;
115 uint32_t FreeTotal;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116 struct eun_info_t {
David Woodhouse3854be72008-12-10 14:06:42 +0000117 uint32_t Offset;
118 uint32_t EraseCount;
119 uint32_t Free;
120 uint32_t Deleted;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121 } *EUNInfo;
122 struct xfer_info_t {
David Woodhouse3854be72008-12-10 14:06:42 +0000123 uint32_t Offset;
124 uint32_t EraseCount;
125 uint16_t state;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126 } *XferInfo;
David Woodhouse3854be72008-12-10 14:06:42 +0000127 uint16_t bam_index;
128 uint32_t *bam_cache;
129 uint16_t DataUnits;
130 uint32_t BlocksPerUnit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 erase_unit_header_t header;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132} partition_t;
133
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134/* Partition state flags */
135#define FTL_FORMATTED 0x01
136
137/* Transfer unit states */
138#define XFER_UNKNOWN 0x00
139#define XFER_ERASING 0x01
140#define XFER_ERASED 0x02
141#define XFER_PREPARED 0x03
142#define XFER_FAILED 0x04
143
144/*====================================================================*/
145
146
147static void ftl_erase_callback(struct erase_info *done);
148
149
150/*======================================================================
151
152 Scan_header() checks to see if a memory region contains an FTL
153 partition. build_maps() reads all the erase unit headers, builds
154 the erase unit map, and then builds the virtual page map.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000155
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156======================================================================*/
157
158static int scan_header(partition_t *part)
159{
160 erase_unit_header_t header;
161 loff_t offset, max_offset;
162 size_t ret;
163 int err;
164 part->header.FormattedSize = 0;
165 max_offset = (0x100000<part->mbd.mtd->size)?0x100000:part->mbd.mtd->size;
166 /* Search first megabyte for a valid FTL header */
167 for (offset = 0;
168 (offset + sizeof(header)) < max_offset;
169 offset += part->mbd.mtd->erasesize ? : 0x2000) {
170
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200171 err = mtd_read(part->mbd.mtd, offset, sizeof(header), &ret,
172 (unsigned char *)&header);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000173
174 if (err)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175 return err;
176
177 if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break;
178 }
179
180 if (offset == max_offset) {
181 printk(KERN_NOTICE "ftl_cs: FTL header not found.\n");
182 return -ENOENT;
183 }
184 if (header.BlockSize != 9 ||
185 (header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) ||
186 (header.NumTransferUnits >= le16_to_cpu(header.NumEraseUnits))) {
187 printk(KERN_NOTICE "ftl_cs: FTL header corrupt!\n");
188 return -1;
189 }
190 if ((1 << header.EraseUnitSize) != part->mbd.mtd->erasesize) {
191 printk(KERN_NOTICE "ftl: FTL EraseUnitSize %x != MTD erasesize %x\n",
192 1 << header.EraseUnitSize,part->mbd.mtd->erasesize);
193 return -1;
194 }
195 part->header = header;
196 return 0;
197}
198
199static int build_maps(partition_t *part)
200{
201 erase_unit_header_t header;
David Woodhouse3854be72008-12-10 14:06:42 +0000202 uint16_t xvalid, xtrans, i;
203 unsigned blocks, j;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700204 int hdr_ok, ret = -1;
205 ssize_t retval;
206 loff_t offset;
207
208 /* Set up erase unit maps */
209 part->DataUnits = le16_to_cpu(part->header.NumEraseUnits) -
210 part->header.NumTransferUnits;
211 part->EUNInfo = kmalloc(part->DataUnits * sizeof(struct eun_info_t),
212 GFP_KERNEL);
213 if (!part->EUNInfo)
214 goto out;
215 for (i = 0; i < part->DataUnits; i++)
216 part->EUNInfo[i].Offset = 0xffffffff;
217 part->XferInfo =
218 kmalloc(part->header.NumTransferUnits * sizeof(struct xfer_info_t),
219 GFP_KERNEL);
220 if (!part->XferInfo)
221 goto out_EUNInfo;
222
223 xvalid = xtrans = 0;
224 for (i = 0; i < le16_to_cpu(part->header.NumEraseUnits); i++) {
225 offset = ((i + le16_to_cpu(part->header.FirstPhysicalEUN))
226 << part->header.EraseUnitSize);
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200227 ret = mtd_read(part->mbd.mtd, offset, sizeof(header), &retval,
228 (unsigned char *)&header);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000229
230 if (ret)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231 goto out_XferInfo;
232
233 ret = -1;
234 /* Is this a transfer partition? */
235 hdr_ok = (strcmp(header.DataOrgTuple+3, "FTL100") == 0);
236 if (hdr_ok && (le16_to_cpu(header.LogicalEUN) < part->DataUnits) &&
237 (part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset == 0xffffffff)) {
238 part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset = offset;
239 part->EUNInfo[le16_to_cpu(header.LogicalEUN)].EraseCount =
240 le32_to_cpu(header.EraseCount);
241 xvalid++;
242 } else {
243 if (xtrans == part->header.NumTransferUnits) {
244 printk(KERN_NOTICE "ftl_cs: format error: too many "
245 "transfer units!\n");
246 goto out_XferInfo;
247 }
248 if (hdr_ok && (le16_to_cpu(header.LogicalEUN) == 0xffff)) {
249 part->XferInfo[xtrans].state = XFER_PREPARED;
250 part->XferInfo[xtrans].EraseCount = le32_to_cpu(header.EraseCount);
251 } else {
252 part->XferInfo[xtrans].state = XFER_UNKNOWN;
253 /* Pick anything reasonable for the erase count */
254 part->XferInfo[xtrans].EraseCount =
255 le32_to_cpu(part->header.EraseCount);
256 }
257 part->XferInfo[xtrans].Offset = offset;
258 xtrans++;
259 }
260 }
261 /* Check for format trouble */
262 header = part->header;
263 if ((xtrans != header.NumTransferUnits) ||
264 (xvalid+xtrans != le16_to_cpu(header.NumEraseUnits))) {
265 printk(KERN_NOTICE "ftl_cs: format error: erase units "
266 "don't add up!\n");
267 goto out_XferInfo;
268 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000269
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270 /* Set up virtual page map */
271 blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
David Woodhouse3854be72008-12-10 14:06:42 +0000272 part->VirtualBlockMap = vmalloc(blocks * sizeof(uint32_t));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273 if (!part->VirtualBlockMap)
274 goto out_XferInfo;
275
David Woodhouse3854be72008-12-10 14:06:42 +0000276 memset(part->VirtualBlockMap, 0xff, blocks * sizeof(uint32_t));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277 part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
278
David Woodhouse3854be72008-12-10 14:06:42 +0000279 part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(uint32_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700280 GFP_KERNEL);
281 if (!part->bam_cache)
282 goto out_VirtualBlockMap;
283
284 part->bam_index = 0xffff;
285 part->FreeTotal = 0;
286
287 for (i = 0; i < part->DataUnits; i++) {
288 part->EUNInfo[i].Free = 0;
289 part->EUNInfo[i].Deleted = 0;
290 offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000291
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200292 ret = mtd_read(part->mbd.mtd, offset,
293 part->BlocksPerUnit * sizeof(uint32_t), &retval,
294 (unsigned char *)part->bam_cache);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000295
296 if (ret)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700297 goto out_bam_cache;
298
299 for (j = 0; j < part->BlocksPerUnit; j++) {
300 if (BLOCK_FREE(le32_to_cpu(part->bam_cache[j]))) {
301 part->EUNInfo[i].Free++;
302 part->FreeTotal++;
303 } else if ((BLOCK_TYPE(le32_to_cpu(part->bam_cache[j])) == BLOCK_DATA) &&
304 (BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j])) < blocks))
305 part->VirtualBlockMap[BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j]))] =
306 (i << header.EraseUnitSize) + (j << header.BlockSize);
307 else if (BLOCK_DELETED(le32_to_cpu(part->bam_cache[j])))
308 part->EUNInfo[i].Deleted++;
309 }
310 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000311
Linus Torvalds1da177e2005-04-16 15:20:36 -0700312 ret = 0;
313 goto out;
314
315out_bam_cache:
316 kfree(part->bam_cache);
317out_VirtualBlockMap:
318 vfree(part->VirtualBlockMap);
319out_XferInfo:
320 kfree(part->XferInfo);
321out_EUNInfo:
322 kfree(part->EUNInfo);
323out:
324 return ret;
325} /* build_maps */
326
327/*======================================================================
328
329 Erase_xfer() schedules an asynchronous erase operation for a
330 transfer unit.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000331
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332======================================================================*/
333
334static int erase_xfer(partition_t *part,
David Woodhouse3854be72008-12-10 14:06:42 +0000335 uint16_t xfernum)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700336{
337 int ret;
338 struct xfer_info_t *xfer;
339 struct erase_info *erase;
340
341 xfer = &part->XferInfo[xfernum];
Brian Norris289c0522011-07-19 10:06:09 -0700342 pr_debug("ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700343 xfer->state = XFER_ERASING;
344
345 /* Is there a free erase slot? Always in MTD. */
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000346
347
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348 erase=kmalloc(sizeof(struct erase_info), GFP_KERNEL);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000349 if (!erase)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350 return -ENOMEM;
351
Herbert Valerio Riedel8ea2e062005-01-17 13:47:24 +0000352 erase->mtd = part->mbd.mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700353 erase->callback = ftl_erase_callback;
354 erase->addr = xfer->Offset;
355 erase->len = 1 << part->header.EraseUnitSize;
356 erase->priv = (u_long)part;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000357
Artem Bityutskiy7e1f0dc2011-12-23 15:25:39 +0200358 ret = mtd_erase(part->mbd.mtd, erase);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359
360 if (!ret)
361 xfer->EraseCount++;
362 else
363 kfree(erase);
364
365 return ret;
366} /* erase_xfer */
367
368/*======================================================================
369
370 Prepare_xfer() takes a freshly erased transfer unit and gives
371 it an appropriate header.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000372
Linus Torvalds1da177e2005-04-16 15:20:36 -0700373======================================================================*/
374
375static void ftl_erase_callback(struct erase_info *erase)
376{
377 partition_t *part;
378 struct xfer_info_t *xfer;
379 int i;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000380
Linus Torvalds1da177e2005-04-16 15:20:36 -0700381 /* Look up the transfer unit */
382 part = (partition_t *)(erase->priv);
383
384 for (i = 0; i < part->header.NumTransferUnits; i++)
385 if (part->XferInfo[i].Offset == erase->addr) break;
386
387 if (i == part->header.NumTransferUnits) {
388 printk(KERN_NOTICE "ftl_cs: internal error: "
389 "erase lookup failed!\n");
390 return;
391 }
392
393 xfer = &part->XferInfo[i];
394 if (erase->state == MTD_ERASE_DONE)
395 xfer->state = XFER_ERASED;
396 else {
397 xfer->state = XFER_FAILED;
398 printk(KERN_NOTICE "ftl_cs: erase failed: state = %d\n",
399 erase->state);
400 }
401
402 kfree(erase);
403
404} /* ftl_erase_callback */
405
406static int prepare_xfer(partition_t *part, int i)
407{
408 erase_unit_header_t header;
409 struct xfer_info_t *xfer;
410 int nbam, ret;
David Woodhouse3854be72008-12-10 14:06:42 +0000411 uint32_t ctl;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412 ssize_t retlen;
413 loff_t offset;
414
415 xfer = &part->XferInfo[i];
416 xfer->state = XFER_FAILED;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000417
Brian Norris289c0522011-07-19 10:06:09 -0700418 pr_debug("ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419
420 /* Write the transfer unit header */
421 header = part->header;
422 header.LogicalEUN = cpu_to_le16(0xffff);
423 header.EraseCount = cpu_to_le32(xfer->EraseCount);
424
425 ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset, sizeof(header),
426 &retlen, (u_char *)&header);
427
428 if (ret) {
429 return ret;
430 }
431
432 /* Write the BAM stub */
David Woodhouse3854be72008-12-10 14:06:42 +0000433 nbam = (part->BlocksPerUnit * sizeof(uint32_t) +
Linus Torvalds1da177e2005-04-16 15:20:36 -0700434 le32_to_cpu(part->header.BAMOffset) + SECTOR_SIZE - 1) / SECTOR_SIZE;
435
436 offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset);
437 ctl = cpu_to_le32(BLOCK_CONTROL);
438
David Woodhouse3854be72008-12-10 14:06:42 +0000439 for (i = 0; i < nbam; i++, offset += sizeof(uint32_t)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700440
David Woodhouse3854be72008-12-10 14:06:42 +0000441 ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint32_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700442 &retlen, (u_char *)&ctl);
443
444 if (ret)
445 return ret;
446 }
447 xfer->state = XFER_PREPARED;
448 return 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000449
Linus Torvalds1da177e2005-04-16 15:20:36 -0700450} /* prepare_xfer */
451
452/*======================================================================
453
454 Copy_erase_unit() takes a full erase block and a transfer unit,
455 copies everything to the transfer unit, then swaps the block
456 pointers.
457
458 All data blocks are copied to the corresponding blocks in the
459 target unit, so the virtual block map does not need to be
460 updated.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000461
Linus Torvalds1da177e2005-04-16 15:20:36 -0700462======================================================================*/
463
David Woodhouse3854be72008-12-10 14:06:42 +0000464static int copy_erase_unit(partition_t *part, uint16_t srcunit,
465 uint16_t xferunit)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466{
467 u_char buf[SECTOR_SIZE];
468 struct eun_info_t *eun;
469 struct xfer_info_t *xfer;
David Woodhouse3854be72008-12-10 14:06:42 +0000470 uint32_t src, dest, free, i;
471 uint16_t unit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472 int ret;
473 ssize_t retlen;
474 loff_t offset;
David Woodhouse3854be72008-12-10 14:06:42 +0000475 uint16_t srcunitswap = cpu_to_le16(srcunit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700476
477 eun = &part->EUNInfo[srcunit];
478 xfer = &part->XferInfo[xferunit];
Brian Norris289c0522011-07-19 10:06:09 -0700479 pr_debug("ftl_cs: copying block 0x%x to 0x%x\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700480 eun->Offset, xfer->Offset);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000481
482
Linus Torvalds1da177e2005-04-16 15:20:36 -0700483 /* Read current BAM */
484 if (part->bam_index != srcunit) {
485
486 offset = eun->Offset + le32_to_cpu(part->header.BAMOffset);
487
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200488 ret = mtd_read(part->mbd.mtd, offset,
489 part->BlocksPerUnit * sizeof(uint32_t), &retlen,
490 (u_char *)(part->bam_cache));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700491
492 /* mark the cache bad, in case we get an error later */
493 part->bam_index = 0xffff;
494
495 if (ret) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000496 printk( KERN_WARNING "ftl: Failed to read BAM cache in copy_erase_unit()!\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700497 return ret;
498 }
499 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000500
Linus Torvalds1da177e2005-04-16 15:20:36 -0700501 /* Write the LogicalEUN for the transfer unit */
502 xfer->state = XFER_UNKNOWN;
503 offset = xfer->Offset + 20; /* Bad! */
504 unit = cpu_to_le16(0x7fff);
505
David Woodhouse3854be72008-12-10 14:06:42 +0000506 ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint16_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700507 &retlen, (u_char *) &unit);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000508
Linus Torvalds1da177e2005-04-16 15:20:36 -0700509 if (ret) {
510 printk( KERN_WARNING "ftl: Failed to write back to BAM cache in copy_erase_unit()!\n");
511 return ret;
512 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000513
Linus Torvalds1da177e2005-04-16 15:20:36 -0700514 /* Copy all data blocks from source unit to transfer unit */
515 src = eun->Offset; dest = xfer->Offset;
516
517 free = 0;
518 ret = 0;
519 for (i = 0; i < part->BlocksPerUnit; i++) {
520 switch (BLOCK_TYPE(le32_to_cpu(part->bam_cache[i]))) {
521 case BLOCK_CONTROL:
522 /* This gets updated later */
523 break;
524 case BLOCK_DATA:
525 case BLOCK_REPLACEMENT:
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200526 ret = mtd_read(part->mbd.mtd, src, SECTOR_SIZE, &retlen,
527 (u_char *)buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700528 if (ret) {
529 printk(KERN_WARNING "ftl: Error reading old xfer unit in copy_erase_unit\n");
530 return ret;
531 }
532
533
534 ret = part->mbd.mtd->write(part->mbd.mtd, dest, SECTOR_SIZE,
535 &retlen, (u_char *) buf);
536 if (ret) {
537 printk(KERN_WARNING "ftl: Error writing new xfer unit in copy_erase_unit\n");
538 return ret;
539 }
540
541 break;
542 default:
543 /* All other blocks must be free */
544 part->bam_cache[i] = cpu_to_le32(0xffffffff);
545 free++;
546 break;
547 }
548 src += SECTOR_SIZE;
549 dest += SECTOR_SIZE;
550 }
551
552 /* Write the BAM to the transfer unit */
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000553 ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + le32_to_cpu(part->header.BAMOffset),
554 part->BlocksPerUnit * sizeof(int32_t), &retlen,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700555 (u_char *)part->bam_cache);
556 if (ret) {
557 printk( KERN_WARNING "ftl: Error writing BAM in copy_erase_unit\n");
558 return ret;
559 }
560
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000561
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 /* All clear? Then update the LogicalEUN again */
David Woodhouse3854be72008-12-10 14:06:42 +0000563 ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + 20, sizeof(uint16_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564 &retlen, (u_char *)&srcunitswap);
565
566 if (ret) {
567 printk(KERN_WARNING "ftl: Error writing new LogicalEUN in copy_erase_unit\n");
568 return ret;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000569 }
570
571
Linus Torvalds1da177e2005-04-16 15:20:36 -0700572 /* Update the maps and usage stats*/
573 i = xfer->EraseCount;
574 xfer->EraseCount = eun->EraseCount;
575 eun->EraseCount = i;
576 i = xfer->Offset;
577 xfer->Offset = eun->Offset;
578 eun->Offset = i;
579 part->FreeTotal -= eun->Free;
580 part->FreeTotal += free;
581 eun->Free = free;
582 eun->Deleted = 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000583
Linus Torvalds1da177e2005-04-16 15:20:36 -0700584 /* Now, the cache should be valid for the new block */
585 part->bam_index = srcunit;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000586
Linus Torvalds1da177e2005-04-16 15:20:36 -0700587 return 0;
588} /* copy_erase_unit */
589
590/*======================================================================
591
592 reclaim_block() picks a full erase unit and a transfer unit and
593 then calls copy_erase_unit() to copy one to the other. Then, it
594 schedules an erase on the expired block.
595
596 What's a good way to decide which transfer unit and which erase
597 unit to use? Beats me. My way is to always pick the transfer
598 unit with the fewest erases, and usually pick the data unit with
599 the most deleted blocks. But with a small probability, pick the
600 oldest data unit instead. This means that we generally postpone
Brian Norris92394b5c2011-07-20 09:53:42 -0700601 the next reclamation as long as possible, but shuffle static
Linus Torvalds1da177e2005-04-16 15:20:36 -0700602 stuff around a bit for wear leveling.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000603
Linus Torvalds1da177e2005-04-16 15:20:36 -0700604======================================================================*/
605
606static int reclaim_block(partition_t *part)
607{
David Woodhouse3854be72008-12-10 14:06:42 +0000608 uint16_t i, eun, xfer;
609 uint32_t best;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700610 int queued, ret;
611
Brian Norris289c0522011-07-19 10:06:09 -0700612 pr_debug("ftl_cs: reclaiming space...\n");
613 pr_debug("NumTransferUnits == %x\n", part->header.NumTransferUnits);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700614 /* Pick the least erased transfer unit */
615 best = 0xffffffff; xfer = 0xffff;
616 do {
617 queued = 0;
618 for (i = 0; i < part->header.NumTransferUnits; i++) {
619 int n=0;
620 if (part->XferInfo[i].state == XFER_UNKNOWN) {
Brian Norris289c0522011-07-19 10:06:09 -0700621 pr_debug("XferInfo[%d].state == XFER_UNKNOWN\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700622 n=1;
623 erase_xfer(part, i);
624 }
625 if (part->XferInfo[i].state == XFER_ERASING) {
Brian Norris289c0522011-07-19 10:06:09 -0700626 pr_debug("XferInfo[%d].state == XFER_ERASING\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627 n=1;
628 queued = 1;
629 }
630 else if (part->XferInfo[i].state == XFER_ERASED) {
Brian Norris289c0522011-07-19 10:06:09 -0700631 pr_debug("XferInfo[%d].state == XFER_ERASED\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700632 n=1;
633 prepare_xfer(part, i);
634 }
635 if (part->XferInfo[i].state == XFER_PREPARED) {
Brian Norris289c0522011-07-19 10:06:09 -0700636 pr_debug("XferInfo[%d].state == XFER_PREPARED\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700637 n=1;
638 if (part->XferInfo[i].EraseCount <= best) {
639 best = part->XferInfo[i].EraseCount;
640 xfer = i;
641 }
642 }
643 if (!n)
Brian Norris289c0522011-07-19 10:06:09 -0700644 pr_debug("XferInfo[%d].state == %x\n",i, part->XferInfo[i].state);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700645
646 }
647 if (xfer == 0xffff) {
648 if (queued) {
Brian Norris289c0522011-07-19 10:06:09 -0700649 pr_debug("ftl_cs: waiting for transfer "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700650 "unit to be prepared...\n");
651 if (part->mbd.mtd->sync)
652 part->mbd.mtd->sync(part->mbd.mtd);
653 } else {
654 static int ne = 0;
655 if (++ne < 5)
656 printk(KERN_NOTICE "ftl_cs: reclaim failed: no "
657 "suitable transfer units!\n");
658 else
Brian Norris289c0522011-07-19 10:06:09 -0700659 pr_debug("ftl_cs: reclaim failed: no "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700660 "suitable transfer units!\n");
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000661
Linus Torvalds1da177e2005-04-16 15:20:36 -0700662 return -EIO;
663 }
664 }
665 } while (xfer == 0xffff);
666
667 eun = 0;
668 if ((jiffies % shuffle_freq) == 0) {
Brian Norris289c0522011-07-19 10:06:09 -0700669 pr_debug("ftl_cs: recycling freshest block...\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700670 best = 0xffffffff;
671 for (i = 0; i < part->DataUnits; i++)
672 if (part->EUNInfo[i].EraseCount <= best) {
673 best = part->EUNInfo[i].EraseCount;
674 eun = i;
675 }
676 } else {
677 best = 0;
678 for (i = 0; i < part->DataUnits; i++)
679 if (part->EUNInfo[i].Deleted >= best) {
680 best = part->EUNInfo[i].Deleted;
681 eun = i;
682 }
683 if (best == 0) {
684 static int ne = 0;
685 if (++ne < 5)
686 printk(KERN_NOTICE "ftl_cs: reclaim failed: "
687 "no free blocks!\n");
688 else
Brian Norris289c0522011-07-19 10:06:09 -0700689 pr_debug("ftl_cs: reclaim failed: "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700690 "no free blocks!\n");
691
692 return -EIO;
693 }
694 }
695 ret = copy_erase_unit(part, eun, xfer);
696 if (!ret)
697 erase_xfer(part, xfer);
698 else
699 printk(KERN_NOTICE "ftl_cs: copy_erase_unit failed!\n");
700 return ret;
701} /* reclaim_block */
702
703/*======================================================================
704
705 Find_free() searches for a free block. If necessary, it updates
706 the BAM cache for the erase unit containing the free block. It
707 returns the block index -- the erase unit is just the currently
708 cached unit. If there are no free blocks, it returns 0 -- this
709 is never a valid data block because it contains the header.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000710
Linus Torvalds1da177e2005-04-16 15:20:36 -0700711======================================================================*/
712
713#ifdef PSYCHO_DEBUG
714static void dump_lists(partition_t *part)
715{
716 int i;
717 printk(KERN_DEBUG "ftl_cs: Free total = %d\n", part->FreeTotal);
718 for (i = 0; i < part->DataUnits; i++)
719 printk(KERN_DEBUG "ftl_cs: unit %d: %d phys, %d free, "
720 "%d deleted\n", i,
721 part->EUNInfo[i].Offset >> part->header.EraseUnitSize,
722 part->EUNInfo[i].Free, part->EUNInfo[i].Deleted);
723}
724#endif
725
David Woodhouse3854be72008-12-10 14:06:42 +0000726static uint32_t find_free(partition_t *part)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700727{
David Woodhouse3854be72008-12-10 14:06:42 +0000728 uint16_t stop, eun;
729 uint32_t blk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700730 size_t retlen;
731 int ret;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000732
Linus Torvalds1da177e2005-04-16 15:20:36 -0700733 /* Find an erase unit with some free space */
734 stop = (part->bam_index == 0xffff) ? 0 : part->bam_index;
735 eun = stop;
736 do {
737 if (part->EUNInfo[eun].Free != 0) break;
738 /* Wrap around at end of table */
739 if (++eun == part->DataUnits) eun = 0;
740 } while (eun != stop);
741
742 if (part->EUNInfo[eun].Free == 0)
743 return 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000744
Linus Torvalds1da177e2005-04-16 15:20:36 -0700745 /* Is this unit's BAM cached? */
746 if (eun != part->bam_index) {
747 /* Invalidate cache */
748 part->bam_index = 0xffff;
749
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200750 ret = mtd_read(part->mbd.mtd,
751 part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset),
752 part->BlocksPerUnit * sizeof(uint32_t),
753 &retlen,
754 (u_char *)(part->bam_cache));
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000755
Linus Torvalds1da177e2005-04-16 15:20:36 -0700756 if (ret) {
757 printk(KERN_WARNING"ftl: Error reading BAM in find_free\n");
758 return 0;
759 }
760 part->bam_index = eun;
761 }
762
763 /* Find a free block */
764 for (blk = 0; blk < part->BlocksPerUnit; blk++)
765 if (BLOCK_FREE(le32_to_cpu(part->bam_cache[blk]))) break;
766 if (blk == part->BlocksPerUnit) {
767#ifdef PSYCHO_DEBUG
768 static int ne = 0;
769 if (++ne == 1)
770 dump_lists(part);
771#endif
772 printk(KERN_NOTICE "ftl_cs: bad free list!\n");
773 return 0;
774 }
Brian Norris289c0522011-07-19 10:06:09 -0700775 pr_debug("ftl_cs: found free block at %d in %d\n", blk, eun);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700776 return blk;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000777
Linus Torvalds1da177e2005-04-16 15:20:36 -0700778} /* find_free */
779
780
781/*======================================================================
782
783 Read a series of sectors from an FTL partition.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000784
Linus Torvalds1da177e2005-04-16 15:20:36 -0700785======================================================================*/
786
787static int ftl_read(partition_t *part, caddr_t buffer,
788 u_long sector, u_long nblocks)
789{
David Woodhouse3854be72008-12-10 14:06:42 +0000790 uint32_t log_addr, bsize;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700791 u_long i;
792 int ret;
793 size_t offset, retlen;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000794
Brian Norris289c0522011-07-19 10:06:09 -0700795 pr_debug("ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700796 part, sector, nblocks);
797 if (!(part->state & FTL_FORMATTED)) {
798 printk(KERN_NOTICE "ftl_cs: bad partition\n");
799 return -EIO;
800 }
801 bsize = 1 << part->header.EraseUnitSize;
802
803 for (i = 0; i < nblocks; i++) {
804 if (((sector+i) * SECTOR_SIZE) >= le32_to_cpu(part->header.FormattedSize)) {
805 printk(KERN_NOTICE "ftl_cs: bad read offset\n");
806 return -EIO;
807 }
808 log_addr = part->VirtualBlockMap[sector+i];
809 if (log_addr == 0xffffffff)
810 memset(buffer, 0, SECTOR_SIZE);
811 else {
812 offset = (part->EUNInfo[log_addr / bsize].Offset
813 + (log_addr % bsize));
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200814 ret = mtd_read(part->mbd.mtd, offset, SECTOR_SIZE, &retlen,
815 (u_char *)buffer);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700816
817 if (ret) {
818 printk(KERN_WARNING "Error reading MTD device in ftl_read()\n");
819 return ret;
820 }
821 }
822 buffer += SECTOR_SIZE;
823 }
824 return 0;
825} /* ftl_read */
826
827/*======================================================================
828
829 Write a series of sectors to an FTL partition
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000830
Linus Torvalds1da177e2005-04-16 15:20:36 -0700831======================================================================*/
832
David Woodhouse3854be72008-12-10 14:06:42 +0000833static int set_bam_entry(partition_t *part, uint32_t log_addr,
834 uint32_t virt_addr)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700835{
David Woodhouse3854be72008-12-10 14:06:42 +0000836 uint32_t bsize, blk, le_virt_addr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700837#ifdef PSYCHO_DEBUG
David Woodhouse3854be72008-12-10 14:06:42 +0000838 uint32_t old_addr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700839#endif
David Woodhouse3854be72008-12-10 14:06:42 +0000840 uint16_t eun;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700841 int ret;
842 size_t retlen, offset;
843
Brian Norris289c0522011-07-19 10:06:09 -0700844 pr_debug("ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700845 part, log_addr, virt_addr);
846 bsize = 1 << part->header.EraseUnitSize;
847 eun = log_addr / bsize;
848 blk = (log_addr % bsize) / SECTOR_SIZE;
David Woodhouse3854be72008-12-10 14:06:42 +0000849 offset = (part->EUNInfo[eun].Offset + blk * sizeof(uint32_t) +
Linus Torvalds1da177e2005-04-16 15:20:36 -0700850 le32_to_cpu(part->header.BAMOffset));
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000851
Linus Torvalds1da177e2005-04-16 15:20:36 -0700852#ifdef PSYCHO_DEBUG
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200853 ret = mtd_read(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
854 (u_char *)&old_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700855 if (ret) {
856 printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret);
857 return ret;
858 }
859 old_addr = le32_to_cpu(old_addr);
860
861 if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) ||
862 ((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) ||
863 (!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) {
864 static int ne = 0;
865 if (++ne < 5) {
866 printk(KERN_NOTICE "ftl_cs: set_bam_entry() inconsistency!\n");
867 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, old = 0x%x"
868 ", new = 0x%x\n", log_addr, old_addr, virt_addr);
869 }
870 return -EIO;
871 }
872#endif
873 le_virt_addr = cpu_to_le32(virt_addr);
874 if (part->bam_index == eun) {
875#ifdef PSYCHO_DEBUG
876 if (le32_to_cpu(part->bam_cache[blk]) != old_addr) {
877 static int ne = 0;
878 if (++ne < 5) {
879 printk(KERN_NOTICE "ftl_cs: set_bam_entry() "
880 "inconsistency!\n");
881 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, cache"
882 " = 0x%x\n",
883 le32_to_cpu(part->bam_cache[blk]), old_addr);
884 }
885 return -EIO;
886 }
887#endif
888 part->bam_cache[blk] = le_virt_addr;
889 }
David Woodhouse3854be72008-12-10 14:06:42 +0000890 ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint32_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700891 &retlen, (u_char *)&le_virt_addr);
892
893 if (ret) {
894 printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n");
895 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, new = 0x%x\n",
896 log_addr, virt_addr);
897 }
898 return ret;
899} /* set_bam_entry */
900
901static int ftl_write(partition_t *part, caddr_t buffer,
902 u_long sector, u_long nblocks)
903{
David Woodhouse3854be72008-12-10 14:06:42 +0000904 uint32_t bsize, log_addr, virt_addr, old_addr, blk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700905 u_long i;
906 int ret;
907 size_t retlen, offset;
908
Brian Norris289c0522011-07-19 10:06:09 -0700909 pr_debug("ftl_cs: ftl_write(0x%p, %ld, %ld)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700910 part, sector, nblocks);
911 if (!(part->state & FTL_FORMATTED)) {
912 printk(KERN_NOTICE "ftl_cs: bad partition\n");
913 return -EIO;
914 }
915 /* See if we need to reclaim space, before we start */
916 while (part->FreeTotal < nblocks) {
917 ret = reclaim_block(part);
918 if (ret)
919 return ret;
920 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000921
Linus Torvalds1da177e2005-04-16 15:20:36 -0700922 bsize = 1 << part->header.EraseUnitSize;
923
924 virt_addr = sector * SECTOR_SIZE | BLOCK_DATA;
925 for (i = 0; i < nblocks; i++) {
926 if (virt_addr >= le32_to_cpu(part->header.FormattedSize)) {
927 printk(KERN_NOTICE "ftl_cs: bad write offset\n");
928 return -EIO;
929 }
930
931 /* Grab a free block */
932 blk = find_free(part);
933 if (blk == 0) {
934 static int ne = 0;
935 if (++ne < 5)
936 printk(KERN_NOTICE "ftl_cs: internal error: "
937 "no free blocks!\n");
938 return -ENOSPC;
939 }
940
941 /* Tag the BAM entry, and write the new block */
942 log_addr = part->bam_index * bsize + blk * SECTOR_SIZE;
943 part->EUNInfo[part->bam_index].Free--;
944 part->FreeTotal--;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000945 if (set_bam_entry(part, log_addr, 0xfffffffe))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700946 return -EIO;
947 part->EUNInfo[part->bam_index].Deleted++;
948 offset = (part->EUNInfo[part->bam_index].Offset +
949 blk * SECTOR_SIZE);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000950 ret = part->mbd.mtd->write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700951 buffer);
952
953 if (ret) {
954 printk(KERN_NOTICE "ftl_cs: block write failed!\n");
955 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, virt_addr"
956 " = 0x%x, Offset = 0x%zx\n", log_addr, virt_addr,
957 offset);
958 return -EIO;
959 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000960
Linus Torvalds1da177e2005-04-16 15:20:36 -0700961 /* Only delete the old entry when the new entry is ready */
962 old_addr = part->VirtualBlockMap[sector+i];
963 if (old_addr != 0xffffffff) {
964 part->VirtualBlockMap[sector+i] = 0xffffffff;
965 part->EUNInfo[old_addr/bsize].Deleted++;
966 if (set_bam_entry(part, old_addr, 0))
967 return -EIO;
968 }
969
970 /* Finally, set up the new pointers */
971 if (set_bam_entry(part, log_addr, virt_addr))
972 return -EIO;
973 part->VirtualBlockMap[sector+i] = log_addr;
974 part->EUNInfo[part->bam_index].Deleted--;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000975
Linus Torvalds1da177e2005-04-16 15:20:36 -0700976 buffer += SECTOR_SIZE;
977 virt_addr += SECTOR_SIZE;
978 }
979 return 0;
980} /* ftl_write */
981
982static int ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
983{
984 partition_t *part = (void *)dev;
985 u_long sect;
986
987 /* Sort of arbitrary: round size down to 4KiB boundary */
988 sect = le32_to_cpu(part->header.FormattedSize)/SECTOR_SIZE;
989
990 geo->heads = 1;
991 geo->sectors = 8;
992 geo->cylinders = sect >> 3;
993
994 return 0;
995}
996
997static int ftl_readsect(struct mtd_blktrans_dev *dev,
998 unsigned long block, char *buf)
999{
1000 return ftl_read((void *)dev, buf, block, 1);
1001}
1002
1003static int ftl_writesect(struct mtd_blktrans_dev *dev,
1004 unsigned long block, char *buf)
1005{
1006 return ftl_write((void *)dev, buf, block, 1);
1007}
1008
David Woodhousefdc53972008-08-05 18:08:56 +01001009static int ftl_discardsect(struct mtd_blktrans_dev *dev,
1010 unsigned long sector, unsigned nr_sects)
1011{
1012 partition_t *part = (void *)dev;
1013 uint32_t bsize = 1 << part->header.EraseUnitSize;
1014
Brian Norris289c0522011-07-19 10:06:09 -07001015 pr_debug("FTL erase sector %ld for %d sectors\n",
David Woodhousefdc53972008-08-05 18:08:56 +01001016 sector, nr_sects);
1017
1018 while (nr_sects) {
1019 uint32_t old_addr = part->VirtualBlockMap[sector];
1020 if (old_addr != 0xffffffff) {
1021 part->VirtualBlockMap[sector] = 0xffffffff;
1022 part->EUNInfo[old_addr/bsize].Deleted++;
1023 if (set_bam_entry(part, old_addr, 0))
1024 return -EIO;
1025 }
1026 nr_sects--;
1027 sector++;
1028 }
1029
1030 return 0;
1031}
Linus Torvalds1da177e2005-04-16 15:20:36 -07001032/*====================================================================*/
1033
Adrian Bunk5ce45d52008-04-14 17:20:24 +03001034static void ftl_freepart(partition_t *part)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001035{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001036 vfree(part->VirtualBlockMap);
1037 part->VirtualBlockMap = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001038 kfree(part->VirtualPageMap);
1039 part->VirtualPageMap = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001040 kfree(part->EUNInfo);
1041 part->EUNInfo = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001042 kfree(part->XferInfo);
1043 part->XferInfo = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001044 kfree(part->bam_cache);
1045 part->bam_cache = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001046} /* ftl_freepart */
1047
1048static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
1049{
1050 partition_t *partition;
1051
Burman Yan95b93a02006-11-15 21:10:29 +02001052 partition = kzalloc(sizeof(partition_t), GFP_KERNEL);
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001053
Linus Torvalds1da177e2005-04-16 15:20:36 -07001054 if (!partition) {
1055 printk(KERN_WARNING "No memory to scan for FTL on %s\n",
1056 mtd->name);
1057 return;
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001058 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001059
Linus Torvalds1da177e2005-04-16 15:20:36 -07001060 partition->mbd.mtd = mtd;
1061
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001062 if ((scan_header(partition) == 0) &&
Linus Torvalds1da177e2005-04-16 15:20:36 -07001063 (build_maps(partition) == 0)) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001064
Linus Torvalds1da177e2005-04-16 15:20:36 -07001065 partition->state = FTL_FORMATTED;
1066#ifdef PCMCIA_DEBUG
1067 printk(KERN_INFO "ftl_cs: opening %d KiB FTL partition\n",
1068 le32_to_cpu(partition->header.FormattedSize) >> 10);
1069#endif
1070 partition->mbd.size = le32_to_cpu(partition->header.FormattedSize) >> 9;
Richard Purdie19187672006-10-27 09:09:33 +01001071
Linus Torvalds1da177e2005-04-16 15:20:36 -07001072 partition->mbd.tr = tr;
1073 partition->mbd.devnum = -1;
1074 if (!add_mtd_blktrans_dev((void *)partition))
1075 return;
1076 }
1077
1078 ftl_freepart(partition);
1079 kfree(partition);
1080}
1081
1082static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
1083{
1084 del_mtd_blktrans_dev(dev);
1085 ftl_freepart((partition_t *)dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001086}
1087
Adrian Bunk5ce45d52008-04-14 17:20:24 +03001088static struct mtd_blktrans_ops ftl_tr = {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001089 .name = "ftl",
1090 .major = FTL_MAJOR,
1091 .part_bits = PART_BITS,
Richard Purdie19187672006-10-27 09:09:33 +01001092 .blksize = SECTOR_SIZE,
Linus Torvalds1da177e2005-04-16 15:20:36 -07001093 .readsect = ftl_readsect,
1094 .writesect = ftl_writesect,
David Woodhousefdc53972008-08-05 18:08:56 +01001095 .discard = ftl_discardsect,
Linus Torvalds1da177e2005-04-16 15:20:36 -07001096 .getgeo = ftl_getgeo,
1097 .add_mtd = ftl_add_mtd,
1098 .remove_dev = ftl_remove_dev,
1099 .owner = THIS_MODULE,
1100};
1101
Peter Huewe627df232009-06-11 02:23:33 +02001102static int __init init_ftl(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001103{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001104 return register_mtd_blktrans(&ftl_tr);
1105}
1106
1107static void __exit cleanup_ftl(void)
1108{
1109 deregister_mtd_blktrans(&ftl_tr);
1110}
1111
1112module_init(init_ftl);
1113module_exit(cleanup_ftl);
1114
1115
1116MODULE_LICENSE("Dual MPL/GPL");
1117MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
1118MODULE_DESCRIPTION("Support code for Flash Translation Layer, used on PCMCIA devices");