blob: 9fb3b0dcdac20cd653387c6d382a0f92daec68c7 [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;
David Woodhouse3854be72008-12-10 14:06:42 +0000114 uint32_t FreeTotal;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 struct eun_info_t {
David Woodhouse3854be72008-12-10 14:06:42 +0000116 uint32_t Offset;
117 uint32_t EraseCount;
118 uint32_t Free;
119 uint32_t Deleted;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 } *EUNInfo;
121 struct xfer_info_t {
David Woodhouse3854be72008-12-10 14:06:42 +0000122 uint32_t Offset;
123 uint32_t EraseCount;
124 uint16_t state;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700125 } *XferInfo;
David Woodhouse3854be72008-12-10 14:06:42 +0000126 uint16_t bam_index;
127 uint32_t *bam_cache;
128 uint16_t DataUnits;
129 uint32_t BlocksPerUnit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700130 erase_unit_header_t header;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131} partition_t;
132
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133/* Partition state flags */
134#define FTL_FORMATTED 0x01
135
136/* Transfer unit states */
137#define XFER_UNKNOWN 0x00
138#define XFER_ERASING 0x01
139#define XFER_ERASED 0x02
140#define XFER_PREPARED 0x03
141#define XFER_FAILED 0x04
142
143/*====================================================================*/
144
145
146static void ftl_erase_callback(struct erase_info *done);
147
148
149/*======================================================================
150
151 Scan_header() checks to see if a memory region contains an FTL
152 partition. build_maps() reads all the erase unit headers, builds
153 the erase unit map, and then builds the virtual page map.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000154
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155======================================================================*/
156
157static int scan_header(partition_t *part)
158{
159 erase_unit_header_t header;
160 loff_t offset, max_offset;
161 size_t ret;
162 int err;
163 part->header.FormattedSize = 0;
164 max_offset = (0x100000<part->mbd.mtd->size)?0x100000:part->mbd.mtd->size;
165 /* Search first megabyte for a valid FTL header */
166 for (offset = 0;
167 (offset + sizeof(header)) < max_offset;
168 offset += part->mbd.mtd->erasesize ? : 0x2000) {
169
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200170 err = mtd_read(part->mbd.mtd, offset, sizeof(header), &ret,
171 (unsigned char *)&header);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000172
173 if (err)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174 return err;
175
176 if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break;
177 }
178
179 if (offset == max_offset) {
180 printk(KERN_NOTICE "ftl_cs: FTL header not found.\n");
181 return -ENOENT;
182 }
183 if (header.BlockSize != 9 ||
184 (header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) ||
185 (header.NumTransferUnits >= le16_to_cpu(header.NumEraseUnits))) {
186 printk(KERN_NOTICE "ftl_cs: FTL header corrupt!\n");
187 return -1;
188 }
189 if ((1 << header.EraseUnitSize) != part->mbd.mtd->erasesize) {
190 printk(KERN_NOTICE "ftl: FTL EraseUnitSize %x != MTD erasesize %x\n",
191 1 << header.EraseUnitSize,part->mbd.mtd->erasesize);
192 return -1;
193 }
194 part->header = header;
195 return 0;
196}
197
198static int build_maps(partition_t *part)
199{
200 erase_unit_header_t header;
David Woodhouse3854be72008-12-10 14:06:42 +0000201 uint16_t xvalid, xtrans, i;
202 unsigned blocks, j;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203 int hdr_ok, ret = -1;
204 ssize_t retval;
205 loff_t offset;
206
207 /* Set up erase unit maps */
208 part->DataUnits = le16_to_cpu(part->header.NumEraseUnits) -
209 part->header.NumTransferUnits;
210 part->EUNInfo = kmalloc(part->DataUnits * sizeof(struct eun_info_t),
211 GFP_KERNEL);
212 if (!part->EUNInfo)
213 goto out;
214 for (i = 0; i < part->DataUnits; i++)
215 part->EUNInfo[i].Offset = 0xffffffff;
216 part->XferInfo =
217 kmalloc(part->header.NumTransferUnits * sizeof(struct xfer_info_t),
218 GFP_KERNEL);
219 if (!part->XferInfo)
220 goto out_EUNInfo;
221
222 xvalid = xtrans = 0;
223 for (i = 0; i < le16_to_cpu(part->header.NumEraseUnits); i++) {
224 offset = ((i + le16_to_cpu(part->header.FirstPhysicalEUN))
225 << part->header.EraseUnitSize);
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200226 ret = mtd_read(part->mbd.mtd, offset, sizeof(header), &retval,
227 (unsigned char *)&header);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000228
229 if (ret)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700230 goto out_XferInfo;
231
232 ret = -1;
233 /* Is this a transfer partition? */
234 hdr_ok = (strcmp(header.DataOrgTuple+3, "FTL100") == 0);
235 if (hdr_ok && (le16_to_cpu(header.LogicalEUN) < part->DataUnits) &&
236 (part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset == 0xffffffff)) {
237 part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset = offset;
238 part->EUNInfo[le16_to_cpu(header.LogicalEUN)].EraseCount =
239 le32_to_cpu(header.EraseCount);
240 xvalid++;
241 } else {
242 if (xtrans == part->header.NumTransferUnits) {
243 printk(KERN_NOTICE "ftl_cs: format error: too many "
244 "transfer units!\n");
245 goto out_XferInfo;
246 }
247 if (hdr_ok && (le16_to_cpu(header.LogicalEUN) == 0xffff)) {
248 part->XferInfo[xtrans].state = XFER_PREPARED;
249 part->XferInfo[xtrans].EraseCount = le32_to_cpu(header.EraseCount);
250 } else {
251 part->XferInfo[xtrans].state = XFER_UNKNOWN;
252 /* Pick anything reasonable for the erase count */
253 part->XferInfo[xtrans].EraseCount =
254 le32_to_cpu(part->header.EraseCount);
255 }
256 part->XferInfo[xtrans].Offset = offset;
257 xtrans++;
258 }
259 }
260 /* Check for format trouble */
261 header = part->header;
262 if ((xtrans != header.NumTransferUnits) ||
263 (xvalid+xtrans != le16_to_cpu(header.NumEraseUnits))) {
264 printk(KERN_NOTICE "ftl_cs: format error: erase units "
265 "don't add up!\n");
266 goto out_XferInfo;
267 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000268
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269 /* Set up virtual page map */
270 blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
David Woodhouse3854be72008-12-10 14:06:42 +0000271 part->VirtualBlockMap = vmalloc(blocks * sizeof(uint32_t));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700272 if (!part->VirtualBlockMap)
273 goto out_XferInfo;
274
David Woodhouse3854be72008-12-10 14:06:42 +0000275 memset(part->VirtualBlockMap, 0xff, blocks * sizeof(uint32_t));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276 part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
277
David Woodhouse3854be72008-12-10 14:06:42 +0000278 part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(uint32_t),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700279 GFP_KERNEL);
280 if (!part->bam_cache)
281 goto out_VirtualBlockMap;
282
283 part->bam_index = 0xffff;
284 part->FreeTotal = 0;
285
286 for (i = 0; i < part->DataUnits; i++) {
287 part->EUNInfo[i].Free = 0;
288 part->EUNInfo[i].Deleted = 0;
289 offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000290
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200291 ret = mtd_read(part->mbd.mtd, offset,
292 part->BlocksPerUnit * sizeof(uint32_t), &retval,
293 (unsigned char *)part->bam_cache);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000294
295 if (ret)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700296 goto out_bam_cache;
297
298 for (j = 0; j < part->BlocksPerUnit; j++) {
299 if (BLOCK_FREE(le32_to_cpu(part->bam_cache[j]))) {
300 part->EUNInfo[i].Free++;
301 part->FreeTotal++;
302 } else if ((BLOCK_TYPE(le32_to_cpu(part->bam_cache[j])) == BLOCK_DATA) &&
303 (BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j])) < blocks))
304 part->VirtualBlockMap[BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j]))] =
305 (i << header.EraseUnitSize) + (j << header.BlockSize);
306 else if (BLOCK_DELETED(le32_to_cpu(part->bam_cache[j])))
307 part->EUNInfo[i].Deleted++;
308 }
309 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000310
Linus Torvalds1da177e2005-04-16 15:20:36 -0700311 ret = 0;
312 goto out;
313
314out_bam_cache:
315 kfree(part->bam_cache);
316out_VirtualBlockMap:
317 vfree(part->VirtualBlockMap);
318out_XferInfo:
319 kfree(part->XferInfo);
320out_EUNInfo:
321 kfree(part->EUNInfo);
322out:
323 return ret;
324} /* build_maps */
325
326/*======================================================================
327
328 Erase_xfer() schedules an asynchronous erase operation for a
329 transfer unit.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000330
Linus Torvalds1da177e2005-04-16 15:20:36 -0700331======================================================================*/
332
333static int erase_xfer(partition_t *part,
David Woodhouse3854be72008-12-10 14:06:42 +0000334 uint16_t xfernum)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335{
336 int ret;
337 struct xfer_info_t *xfer;
338 struct erase_info *erase;
339
340 xfer = &part->XferInfo[xfernum];
Brian Norris289c0522011-07-19 10:06:09 -0700341 pr_debug("ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342 xfer->state = XFER_ERASING;
343
344 /* Is there a free erase slot? Always in MTD. */
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000345
346
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347 erase=kmalloc(sizeof(struct erase_info), GFP_KERNEL);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000348 if (!erase)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700349 return -ENOMEM;
350
Herbert Valerio Riedel8ea2e062005-01-17 13:47:24 +0000351 erase->mtd = part->mbd.mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352 erase->callback = ftl_erase_callback;
353 erase->addr = xfer->Offset;
354 erase->len = 1 << part->header.EraseUnitSize;
355 erase->priv = (u_long)part;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000356
Artem Bityutskiy7e1f0dc2011-12-23 15:25:39 +0200357 ret = mtd_erase(part->mbd.mtd, erase);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700358
359 if (!ret)
360 xfer->EraseCount++;
361 else
362 kfree(erase);
363
364 return ret;
365} /* erase_xfer */
366
367/*======================================================================
368
369 Prepare_xfer() takes a freshly erased transfer unit and gives
370 it an appropriate header.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000371
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372======================================================================*/
373
374static void ftl_erase_callback(struct erase_info *erase)
375{
376 partition_t *part;
377 struct xfer_info_t *xfer;
378 int i;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000379
Linus Torvalds1da177e2005-04-16 15:20:36 -0700380 /* Look up the transfer unit */
381 part = (partition_t *)(erase->priv);
382
383 for (i = 0; i < part->header.NumTransferUnits; i++)
384 if (part->XferInfo[i].Offset == erase->addr) break;
385
386 if (i == part->header.NumTransferUnits) {
387 printk(KERN_NOTICE "ftl_cs: internal error: "
388 "erase lookup failed!\n");
389 return;
390 }
391
392 xfer = &part->XferInfo[i];
393 if (erase->state == MTD_ERASE_DONE)
394 xfer->state = XFER_ERASED;
395 else {
396 xfer->state = XFER_FAILED;
397 printk(KERN_NOTICE "ftl_cs: erase failed: state = %d\n",
398 erase->state);
399 }
400
401 kfree(erase);
402
403} /* ftl_erase_callback */
404
405static int prepare_xfer(partition_t *part, int i)
406{
407 erase_unit_header_t header;
408 struct xfer_info_t *xfer;
409 int nbam, ret;
David Woodhouse3854be72008-12-10 14:06:42 +0000410 uint32_t ctl;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700411 ssize_t retlen;
412 loff_t offset;
413
414 xfer = &part->XferInfo[i];
415 xfer->state = XFER_FAILED;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000416
Brian Norris289c0522011-07-19 10:06:09 -0700417 pr_debug("ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700418
419 /* Write the transfer unit header */
420 header = part->header;
421 header.LogicalEUN = cpu_to_le16(0xffff);
422 header.EraseCount = cpu_to_le32(xfer->EraseCount);
423
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200424 ret = mtd_write(part->mbd.mtd, xfer->Offset, sizeof(header), &retlen,
425 (u_char *)&header);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700426
427 if (ret) {
428 return ret;
429 }
430
431 /* Write the BAM stub */
David Woodhouse3854be72008-12-10 14:06:42 +0000432 nbam = (part->BlocksPerUnit * sizeof(uint32_t) +
Linus Torvalds1da177e2005-04-16 15:20:36 -0700433 le32_to_cpu(part->header.BAMOffset) + SECTOR_SIZE - 1) / SECTOR_SIZE;
434
435 offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset);
436 ctl = cpu_to_le32(BLOCK_CONTROL);
437
David Woodhouse3854be72008-12-10 14:06:42 +0000438 for (i = 0; i < nbam; i++, offset += sizeof(uint32_t)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700439
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200440 ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
441 (u_char *)&ctl);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700442
443 if (ret)
444 return ret;
445 }
446 xfer->state = XFER_PREPARED;
447 return 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000448
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449} /* prepare_xfer */
450
451/*======================================================================
452
453 Copy_erase_unit() takes a full erase block and a transfer unit,
454 copies everything to the transfer unit, then swaps the block
455 pointers.
456
457 All data blocks are copied to the corresponding blocks in the
458 target unit, so the virtual block map does not need to be
459 updated.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000460
Linus Torvalds1da177e2005-04-16 15:20:36 -0700461======================================================================*/
462
David Woodhouse3854be72008-12-10 14:06:42 +0000463static int copy_erase_unit(partition_t *part, uint16_t srcunit,
464 uint16_t xferunit)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700465{
466 u_char buf[SECTOR_SIZE];
467 struct eun_info_t *eun;
468 struct xfer_info_t *xfer;
David Woodhouse3854be72008-12-10 14:06:42 +0000469 uint32_t src, dest, free, i;
470 uint16_t unit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700471 int ret;
472 ssize_t retlen;
473 loff_t offset;
David Woodhouse3854be72008-12-10 14:06:42 +0000474 uint16_t srcunitswap = cpu_to_le16(srcunit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700475
476 eun = &part->EUNInfo[srcunit];
477 xfer = &part->XferInfo[xferunit];
Brian Norris289c0522011-07-19 10:06:09 -0700478 pr_debug("ftl_cs: copying block 0x%x to 0x%x\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700479 eun->Offset, xfer->Offset);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000480
481
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482 /* Read current BAM */
483 if (part->bam_index != srcunit) {
484
485 offset = eun->Offset + le32_to_cpu(part->header.BAMOffset);
486
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200487 ret = mtd_read(part->mbd.mtd, offset,
488 part->BlocksPerUnit * sizeof(uint32_t), &retlen,
489 (u_char *)(part->bam_cache));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700490
491 /* mark the cache bad, in case we get an error later */
492 part->bam_index = 0xffff;
493
494 if (ret) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000495 printk( KERN_WARNING "ftl: Failed to read BAM cache in copy_erase_unit()!\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700496 return ret;
497 }
498 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000499
Linus Torvalds1da177e2005-04-16 15:20:36 -0700500 /* Write the LogicalEUN for the transfer unit */
501 xfer->state = XFER_UNKNOWN;
502 offset = xfer->Offset + 20; /* Bad! */
503 unit = cpu_to_le16(0x7fff);
504
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200505 ret = mtd_write(part->mbd.mtd, offset, sizeof(uint16_t), &retlen,
506 (u_char *)&unit);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000507
Linus Torvalds1da177e2005-04-16 15:20:36 -0700508 if (ret) {
509 printk( KERN_WARNING "ftl: Failed to write back to BAM cache in copy_erase_unit()!\n");
510 return ret;
511 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000512
Linus Torvalds1da177e2005-04-16 15:20:36 -0700513 /* Copy all data blocks from source unit to transfer unit */
514 src = eun->Offset; dest = xfer->Offset;
515
516 free = 0;
517 ret = 0;
518 for (i = 0; i < part->BlocksPerUnit; i++) {
519 switch (BLOCK_TYPE(le32_to_cpu(part->bam_cache[i]))) {
520 case BLOCK_CONTROL:
521 /* This gets updated later */
522 break;
523 case BLOCK_DATA:
524 case BLOCK_REPLACEMENT:
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200525 ret = mtd_read(part->mbd.mtd, src, SECTOR_SIZE, &retlen,
526 (u_char *)buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700527 if (ret) {
528 printk(KERN_WARNING "ftl: Error reading old xfer unit in copy_erase_unit\n");
529 return ret;
530 }
531
532
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200533 ret = mtd_write(part->mbd.mtd, dest, SECTOR_SIZE, &retlen,
534 (u_char *)buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700535 if (ret) {
536 printk(KERN_WARNING "ftl: Error writing new xfer unit in copy_erase_unit\n");
537 return ret;
538 }
539
540 break;
541 default:
542 /* All other blocks must be free */
543 part->bam_cache[i] = cpu_to_le32(0xffffffff);
544 free++;
545 break;
546 }
547 src += SECTOR_SIZE;
548 dest += SECTOR_SIZE;
549 }
550
551 /* Write the BAM to the transfer unit */
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200552 ret = mtd_write(part->mbd.mtd,
553 xfer->Offset + le32_to_cpu(part->header.BAMOffset),
554 part->BlocksPerUnit * sizeof(int32_t),
555 &retlen,
556 (u_char *)part->bam_cache);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557 if (ret) {
558 printk( KERN_WARNING "ftl: Error writing BAM in copy_erase_unit\n");
559 return ret;
560 }
561
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000562
Linus Torvalds1da177e2005-04-16 15:20:36 -0700563 /* All clear? Then update the LogicalEUN again */
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200564 ret = mtd_write(part->mbd.mtd, xfer->Offset + 20, sizeof(uint16_t),
565 &retlen, (u_char *)&srcunitswap);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700566
567 if (ret) {
568 printk(KERN_WARNING "ftl: Error writing new LogicalEUN in copy_erase_unit\n");
569 return ret;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000570 }
571
572
Linus Torvalds1da177e2005-04-16 15:20:36 -0700573 /* Update the maps and usage stats*/
Fabian Frederick6166a762015-06-10 18:31:06 +0200574 swap(xfer->EraseCount, eun->EraseCount);
575 swap(xfer->Offset, eun->Offset);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700576 part->FreeTotal -= eun->Free;
577 part->FreeTotal += free;
578 eun->Free = free;
579 eun->Deleted = 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000580
Linus Torvalds1da177e2005-04-16 15:20:36 -0700581 /* Now, the cache should be valid for the new block */
582 part->bam_index = srcunit;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000583
Linus Torvalds1da177e2005-04-16 15:20:36 -0700584 return 0;
585} /* copy_erase_unit */
586
587/*======================================================================
588
589 reclaim_block() picks a full erase unit and a transfer unit and
590 then calls copy_erase_unit() to copy one to the other. Then, it
591 schedules an erase on the expired block.
592
593 What's a good way to decide which transfer unit and which erase
594 unit to use? Beats me. My way is to always pick the transfer
595 unit with the fewest erases, and usually pick the data unit with
596 the most deleted blocks. But with a small probability, pick the
597 oldest data unit instead. This means that we generally postpone
Brian Norris92394b5c2011-07-20 09:53:42 -0700598 the next reclamation as long as possible, but shuffle static
Linus Torvalds1da177e2005-04-16 15:20:36 -0700599 stuff around a bit for wear leveling.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000600
Linus Torvalds1da177e2005-04-16 15:20:36 -0700601======================================================================*/
602
603static int reclaim_block(partition_t *part)
604{
David Woodhouse3854be72008-12-10 14:06:42 +0000605 uint16_t i, eun, xfer;
606 uint32_t best;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700607 int queued, ret;
608
Brian Norris289c0522011-07-19 10:06:09 -0700609 pr_debug("ftl_cs: reclaiming space...\n");
610 pr_debug("NumTransferUnits == %x\n", part->header.NumTransferUnits);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700611 /* Pick the least erased transfer unit */
612 best = 0xffffffff; xfer = 0xffff;
613 do {
614 queued = 0;
615 for (i = 0; i < part->header.NumTransferUnits; i++) {
616 int n=0;
617 if (part->XferInfo[i].state == XFER_UNKNOWN) {
Brian Norris289c0522011-07-19 10:06:09 -0700618 pr_debug("XferInfo[%d].state == XFER_UNKNOWN\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700619 n=1;
620 erase_xfer(part, i);
621 }
622 if (part->XferInfo[i].state == XFER_ERASING) {
Brian Norris289c0522011-07-19 10:06:09 -0700623 pr_debug("XferInfo[%d].state == XFER_ERASING\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700624 n=1;
625 queued = 1;
626 }
627 else if (part->XferInfo[i].state == XFER_ERASED) {
Brian Norris289c0522011-07-19 10:06:09 -0700628 pr_debug("XferInfo[%d].state == XFER_ERASED\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700629 n=1;
630 prepare_xfer(part, i);
631 }
632 if (part->XferInfo[i].state == XFER_PREPARED) {
Brian Norris289c0522011-07-19 10:06:09 -0700633 pr_debug("XferInfo[%d].state == XFER_PREPARED\n",i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700634 n=1;
635 if (part->XferInfo[i].EraseCount <= best) {
636 best = part->XferInfo[i].EraseCount;
637 xfer = i;
638 }
639 }
640 if (!n)
Brian Norris289c0522011-07-19 10:06:09 -0700641 pr_debug("XferInfo[%d].state == %x\n",i, part->XferInfo[i].state);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700642
643 }
644 if (xfer == 0xffff) {
645 if (queued) {
Brian Norris289c0522011-07-19 10:06:09 -0700646 pr_debug("ftl_cs: waiting for transfer "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700647 "unit to be prepared...\n");
Artem Bityutskiy327cf292011-12-30 16:35:35 +0200648 mtd_sync(part->mbd.mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649 } else {
650 static int ne = 0;
651 if (++ne < 5)
652 printk(KERN_NOTICE "ftl_cs: reclaim failed: no "
653 "suitable transfer units!\n");
654 else
Brian Norris289c0522011-07-19 10:06:09 -0700655 pr_debug("ftl_cs: reclaim failed: no "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700656 "suitable transfer units!\n");
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000657
Linus Torvalds1da177e2005-04-16 15:20:36 -0700658 return -EIO;
659 }
660 }
661 } while (xfer == 0xffff);
662
663 eun = 0;
664 if ((jiffies % shuffle_freq) == 0) {
Brian Norris289c0522011-07-19 10:06:09 -0700665 pr_debug("ftl_cs: recycling freshest block...\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700666 best = 0xffffffff;
667 for (i = 0; i < part->DataUnits; i++)
668 if (part->EUNInfo[i].EraseCount <= best) {
669 best = part->EUNInfo[i].EraseCount;
670 eun = i;
671 }
672 } else {
673 best = 0;
674 for (i = 0; i < part->DataUnits; i++)
675 if (part->EUNInfo[i].Deleted >= best) {
676 best = part->EUNInfo[i].Deleted;
677 eun = i;
678 }
679 if (best == 0) {
680 static int ne = 0;
681 if (++ne < 5)
682 printk(KERN_NOTICE "ftl_cs: reclaim failed: "
683 "no free blocks!\n");
684 else
Brian Norris289c0522011-07-19 10:06:09 -0700685 pr_debug("ftl_cs: reclaim failed: "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700686 "no free blocks!\n");
687
688 return -EIO;
689 }
690 }
691 ret = copy_erase_unit(part, eun, xfer);
692 if (!ret)
693 erase_xfer(part, xfer);
694 else
695 printk(KERN_NOTICE "ftl_cs: copy_erase_unit failed!\n");
696 return ret;
697} /* reclaim_block */
698
699/*======================================================================
700
701 Find_free() searches for a free block. If necessary, it updates
702 the BAM cache for the erase unit containing the free block. It
703 returns the block index -- the erase unit is just the currently
704 cached unit. If there are no free blocks, it returns 0 -- this
705 is never a valid data block because it contains the header.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000706
Linus Torvalds1da177e2005-04-16 15:20:36 -0700707======================================================================*/
708
709#ifdef PSYCHO_DEBUG
710static void dump_lists(partition_t *part)
711{
712 int i;
713 printk(KERN_DEBUG "ftl_cs: Free total = %d\n", part->FreeTotal);
714 for (i = 0; i < part->DataUnits; i++)
715 printk(KERN_DEBUG "ftl_cs: unit %d: %d phys, %d free, "
716 "%d deleted\n", i,
717 part->EUNInfo[i].Offset >> part->header.EraseUnitSize,
718 part->EUNInfo[i].Free, part->EUNInfo[i].Deleted);
719}
720#endif
721
David Woodhouse3854be72008-12-10 14:06:42 +0000722static uint32_t find_free(partition_t *part)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700723{
David Woodhouse3854be72008-12-10 14:06:42 +0000724 uint16_t stop, eun;
725 uint32_t blk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700726 size_t retlen;
727 int ret;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000728
Linus Torvalds1da177e2005-04-16 15:20:36 -0700729 /* Find an erase unit with some free space */
730 stop = (part->bam_index == 0xffff) ? 0 : part->bam_index;
731 eun = stop;
732 do {
733 if (part->EUNInfo[eun].Free != 0) break;
734 /* Wrap around at end of table */
735 if (++eun == part->DataUnits) eun = 0;
736 } while (eun != stop);
737
738 if (part->EUNInfo[eun].Free == 0)
739 return 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000740
Linus Torvalds1da177e2005-04-16 15:20:36 -0700741 /* Is this unit's BAM cached? */
742 if (eun != part->bam_index) {
743 /* Invalidate cache */
744 part->bam_index = 0xffff;
745
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200746 ret = mtd_read(part->mbd.mtd,
747 part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset),
748 part->BlocksPerUnit * sizeof(uint32_t),
749 &retlen,
750 (u_char *)(part->bam_cache));
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000751
Linus Torvalds1da177e2005-04-16 15:20:36 -0700752 if (ret) {
753 printk(KERN_WARNING"ftl: Error reading BAM in find_free\n");
754 return 0;
755 }
756 part->bam_index = eun;
757 }
758
759 /* Find a free block */
760 for (blk = 0; blk < part->BlocksPerUnit; blk++)
761 if (BLOCK_FREE(le32_to_cpu(part->bam_cache[blk]))) break;
762 if (blk == part->BlocksPerUnit) {
763#ifdef PSYCHO_DEBUG
764 static int ne = 0;
765 if (++ne == 1)
766 dump_lists(part);
767#endif
768 printk(KERN_NOTICE "ftl_cs: bad free list!\n");
769 return 0;
770 }
Brian Norris289c0522011-07-19 10:06:09 -0700771 pr_debug("ftl_cs: found free block at %d in %d\n", blk, eun);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700772 return blk;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000773
Linus Torvalds1da177e2005-04-16 15:20:36 -0700774} /* find_free */
775
776
777/*======================================================================
778
779 Read a series of sectors from an FTL partition.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000780
Linus Torvalds1da177e2005-04-16 15:20:36 -0700781======================================================================*/
782
783static int ftl_read(partition_t *part, caddr_t buffer,
784 u_long sector, u_long nblocks)
785{
David Woodhouse3854be72008-12-10 14:06:42 +0000786 uint32_t log_addr, bsize;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700787 u_long i;
788 int ret;
789 size_t offset, retlen;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000790
Brian Norris289c0522011-07-19 10:06:09 -0700791 pr_debug("ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700792 part, sector, nblocks);
793 if (!(part->state & FTL_FORMATTED)) {
794 printk(KERN_NOTICE "ftl_cs: bad partition\n");
795 return -EIO;
796 }
797 bsize = 1 << part->header.EraseUnitSize;
798
799 for (i = 0; i < nblocks; i++) {
800 if (((sector+i) * SECTOR_SIZE) >= le32_to_cpu(part->header.FormattedSize)) {
801 printk(KERN_NOTICE "ftl_cs: bad read offset\n");
802 return -EIO;
803 }
804 log_addr = part->VirtualBlockMap[sector+i];
805 if (log_addr == 0xffffffff)
806 memset(buffer, 0, SECTOR_SIZE);
807 else {
808 offset = (part->EUNInfo[log_addr / bsize].Offset
809 + (log_addr % bsize));
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200810 ret = mtd_read(part->mbd.mtd, offset, SECTOR_SIZE, &retlen,
811 (u_char *)buffer);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700812
813 if (ret) {
814 printk(KERN_WARNING "Error reading MTD device in ftl_read()\n");
815 return ret;
816 }
817 }
818 buffer += SECTOR_SIZE;
819 }
820 return 0;
821} /* ftl_read */
822
823/*======================================================================
824
825 Write a series of sectors to an FTL partition
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000826
Linus Torvalds1da177e2005-04-16 15:20:36 -0700827======================================================================*/
828
David Woodhouse3854be72008-12-10 14:06:42 +0000829static int set_bam_entry(partition_t *part, uint32_t log_addr,
830 uint32_t virt_addr)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700831{
David Woodhouse3854be72008-12-10 14:06:42 +0000832 uint32_t bsize, blk, le_virt_addr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700833#ifdef PSYCHO_DEBUG
David Woodhouse3854be72008-12-10 14:06:42 +0000834 uint32_t old_addr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700835#endif
David Woodhouse3854be72008-12-10 14:06:42 +0000836 uint16_t eun;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700837 int ret;
838 size_t retlen, offset;
839
Brian Norris289c0522011-07-19 10:06:09 -0700840 pr_debug("ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700841 part, log_addr, virt_addr);
842 bsize = 1 << part->header.EraseUnitSize;
843 eun = log_addr / bsize;
844 blk = (log_addr % bsize) / SECTOR_SIZE;
David Woodhouse3854be72008-12-10 14:06:42 +0000845 offset = (part->EUNInfo[eun].Offset + blk * sizeof(uint32_t) +
Linus Torvalds1da177e2005-04-16 15:20:36 -0700846 le32_to_cpu(part->header.BAMOffset));
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000847
Linus Torvalds1da177e2005-04-16 15:20:36 -0700848#ifdef PSYCHO_DEBUG
Artem Bityutskiy329ad392011-12-23 17:30:16 +0200849 ret = mtd_read(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
850 (u_char *)&old_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700851 if (ret) {
852 printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret);
853 return ret;
854 }
855 old_addr = le32_to_cpu(old_addr);
856
857 if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) ||
858 ((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) ||
859 (!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) {
860 static int ne = 0;
861 if (++ne < 5) {
862 printk(KERN_NOTICE "ftl_cs: set_bam_entry() inconsistency!\n");
863 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, old = 0x%x"
864 ", new = 0x%x\n", log_addr, old_addr, virt_addr);
865 }
866 return -EIO;
867 }
868#endif
869 le_virt_addr = cpu_to_le32(virt_addr);
870 if (part->bam_index == eun) {
871#ifdef PSYCHO_DEBUG
872 if (le32_to_cpu(part->bam_cache[blk]) != old_addr) {
873 static int ne = 0;
874 if (++ne < 5) {
875 printk(KERN_NOTICE "ftl_cs: set_bam_entry() "
876 "inconsistency!\n");
877 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, cache"
878 " = 0x%x\n",
879 le32_to_cpu(part->bam_cache[blk]), old_addr);
880 }
881 return -EIO;
882 }
883#endif
884 part->bam_cache[blk] = le_virt_addr;
885 }
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200886 ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen,
887 (u_char *)&le_virt_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700888
889 if (ret) {
890 printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n");
891 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, new = 0x%x\n",
892 log_addr, virt_addr);
893 }
894 return ret;
895} /* set_bam_entry */
896
897static int ftl_write(partition_t *part, caddr_t buffer,
898 u_long sector, u_long nblocks)
899{
David Woodhouse3854be72008-12-10 14:06:42 +0000900 uint32_t bsize, log_addr, virt_addr, old_addr, blk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700901 u_long i;
902 int ret;
903 size_t retlen, offset;
904
Brian Norris289c0522011-07-19 10:06:09 -0700905 pr_debug("ftl_cs: ftl_write(0x%p, %ld, %ld)\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700906 part, sector, nblocks);
907 if (!(part->state & FTL_FORMATTED)) {
908 printk(KERN_NOTICE "ftl_cs: bad partition\n");
909 return -EIO;
910 }
911 /* See if we need to reclaim space, before we start */
912 while (part->FreeTotal < nblocks) {
913 ret = reclaim_block(part);
914 if (ret)
915 return ret;
916 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000917
Linus Torvalds1da177e2005-04-16 15:20:36 -0700918 bsize = 1 << part->header.EraseUnitSize;
919
920 virt_addr = sector * SECTOR_SIZE | BLOCK_DATA;
921 for (i = 0; i < nblocks; i++) {
922 if (virt_addr >= le32_to_cpu(part->header.FormattedSize)) {
923 printk(KERN_NOTICE "ftl_cs: bad write offset\n");
924 return -EIO;
925 }
926
927 /* Grab a free block */
928 blk = find_free(part);
929 if (blk == 0) {
930 static int ne = 0;
931 if (++ne < 5)
932 printk(KERN_NOTICE "ftl_cs: internal error: "
933 "no free blocks!\n");
934 return -ENOSPC;
935 }
936
937 /* Tag the BAM entry, and write the new block */
938 log_addr = part->bam_index * bsize + blk * SECTOR_SIZE;
939 part->EUNInfo[part->bam_index].Free--;
940 part->FreeTotal--;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000941 if (set_bam_entry(part, log_addr, 0xfffffffe))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700942 return -EIO;
943 part->EUNInfo[part->bam_index].Deleted++;
944 offset = (part->EUNInfo[part->bam_index].Offset +
945 blk * SECTOR_SIZE);
Artem Bityutskiyeda95cb2011-12-23 17:35:41 +0200946 ret = mtd_write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen, buffer);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700947
948 if (ret) {
949 printk(KERN_NOTICE "ftl_cs: block write failed!\n");
950 printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, virt_addr"
951 " = 0x%x, Offset = 0x%zx\n", log_addr, virt_addr,
952 offset);
953 return -EIO;
954 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000955
Linus Torvalds1da177e2005-04-16 15:20:36 -0700956 /* Only delete the old entry when the new entry is ready */
957 old_addr = part->VirtualBlockMap[sector+i];
958 if (old_addr != 0xffffffff) {
959 part->VirtualBlockMap[sector+i] = 0xffffffff;
960 part->EUNInfo[old_addr/bsize].Deleted++;
961 if (set_bam_entry(part, old_addr, 0))
962 return -EIO;
963 }
964
965 /* Finally, set up the new pointers */
966 if (set_bam_entry(part, log_addr, virt_addr))
967 return -EIO;
968 part->VirtualBlockMap[sector+i] = log_addr;
969 part->EUNInfo[part->bam_index].Deleted--;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000970
Linus Torvalds1da177e2005-04-16 15:20:36 -0700971 buffer += SECTOR_SIZE;
972 virt_addr += SECTOR_SIZE;
973 }
974 return 0;
975} /* ftl_write */
976
977static int ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
978{
979 partition_t *part = (void *)dev;
980 u_long sect;
981
982 /* Sort of arbitrary: round size down to 4KiB boundary */
983 sect = le32_to_cpu(part->header.FormattedSize)/SECTOR_SIZE;
984
985 geo->heads = 1;
986 geo->sectors = 8;
987 geo->cylinders = sect >> 3;
988
989 return 0;
990}
991
992static int ftl_readsect(struct mtd_blktrans_dev *dev,
993 unsigned long block, char *buf)
994{
995 return ftl_read((void *)dev, buf, block, 1);
996}
997
998static int ftl_writesect(struct mtd_blktrans_dev *dev,
999 unsigned long block, char *buf)
1000{
1001 return ftl_write((void *)dev, buf, block, 1);
1002}
1003
David Woodhousefdc53972008-08-05 18:08:56 +01001004static int ftl_discardsect(struct mtd_blktrans_dev *dev,
1005 unsigned long sector, unsigned nr_sects)
1006{
1007 partition_t *part = (void *)dev;
1008 uint32_t bsize = 1 << part->header.EraseUnitSize;
1009
Brian Norris289c0522011-07-19 10:06:09 -07001010 pr_debug("FTL erase sector %ld for %d sectors\n",
David Woodhousefdc53972008-08-05 18:08:56 +01001011 sector, nr_sects);
1012
1013 while (nr_sects) {
1014 uint32_t old_addr = part->VirtualBlockMap[sector];
1015 if (old_addr != 0xffffffff) {
1016 part->VirtualBlockMap[sector] = 0xffffffff;
1017 part->EUNInfo[old_addr/bsize].Deleted++;
1018 if (set_bam_entry(part, old_addr, 0))
1019 return -EIO;
1020 }
1021 nr_sects--;
1022 sector++;
1023 }
1024
1025 return 0;
1026}
Linus Torvalds1da177e2005-04-16 15:20:36 -07001027/*====================================================================*/
1028
Adrian Bunk5ce45d52008-04-14 17:20:24 +03001029static void ftl_freepart(partition_t *part)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001030{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001031 vfree(part->VirtualBlockMap);
1032 part->VirtualBlockMap = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001033 kfree(part->EUNInfo);
1034 part->EUNInfo = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001035 kfree(part->XferInfo);
1036 part->XferInfo = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001037 kfree(part->bam_cache);
1038 part->bam_cache = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001039} /* ftl_freepart */
1040
1041static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
1042{
1043 partition_t *partition;
1044
Burman Yan95b93a02006-11-15 21:10:29 +02001045 partition = kzalloc(sizeof(partition_t), GFP_KERNEL);
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001046
Linus Torvalds1da177e2005-04-16 15:20:36 -07001047 if (!partition) {
1048 printk(KERN_WARNING "No memory to scan for FTL on %s\n",
1049 mtd->name);
1050 return;
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001051 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001052
Linus Torvalds1da177e2005-04-16 15:20:36 -07001053 partition->mbd.mtd = mtd;
1054
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001055 if ((scan_header(partition) == 0) &&
Linus Torvalds1da177e2005-04-16 15:20:36 -07001056 (build_maps(partition) == 0)) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001057
Linus Torvalds1da177e2005-04-16 15:20:36 -07001058 partition->state = FTL_FORMATTED;
1059#ifdef PCMCIA_DEBUG
1060 printk(KERN_INFO "ftl_cs: opening %d KiB FTL partition\n",
1061 le32_to_cpu(partition->header.FormattedSize) >> 10);
1062#endif
1063 partition->mbd.size = le32_to_cpu(partition->header.FormattedSize) >> 9;
Richard Purdie19187672006-10-27 09:09:33 +01001064
Linus Torvalds1da177e2005-04-16 15:20:36 -07001065 partition->mbd.tr = tr;
1066 partition->mbd.devnum = -1;
1067 if (!add_mtd_blktrans_dev((void *)partition))
1068 return;
1069 }
1070
Linus Torvalds1da177e2005-04-16 15:20:36 -07001071 kfree(partition);
1072}
1073
1074static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
1075{
1076 del_mtd_blktrans_dev(dev);
1077 ftl_freepart((partition_t *)dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001078}
1079
Adrian Bunk5ce45d52008-04-14 17:20:24 +03001080static struct mtd_blktrans_ops ftl_tr = {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001081 .name = "ftl",
1082 .major = FTL_MAJOR,
1083 .part_bits = PART_BITS,
Richard Purdie19187672006-10-27 09:09:33 +01001084 .blksize = SECTOR_SIZE,
Linus Torvalds1da177e2005-04-16 15:20:36 -07001085 .readsect = ftl_readsect,
1086 .writesect = ftl_writesect,
David Woodhousefdc53972008-08-05 18:08:56 +01001087 .discard = ftl_discardsect,
Linus Torvalds1da177e2005-04-16 15:20:36 -07001088 .getgeo = ftl_getgeo,
1089 .add_mtd = ftl_add_mtd,
1090 .remove_dev = ftl_remove_dev,
1091 .owner = THIS_MODULE,
1092};
1093
Peter Huewe627df232009-06-11 02:23:33 +02001094static int __init init_ftl(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001095{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001096 return register_mtd_blktrans(&ftl_tr);
1097}
1098
1099static void __exit cleanup_ftl(void)
1100{
1101 deregister_mtd_blktrans(&ftl_tr);
1102}
1103
1104module_init(init_ftl);
1105module_exit(cleanup_ftl);
1106
1107
1108MODULE_LICENSE("Dual MPL/GPL");
1109MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
1110MODULE_DESCRIPTION("Support code for Flash Translation Layer, used on PCMCIA devices");