xref: /linux/drivers/mtd/rfd_ftl.c (revision e27a9960)
1*e27a9960SSean Young /*
2*e27a9960SSean Young  * rfd_ftl.c -- resident flash disk (flash translation layer)
3*e27a9960SSean Young  *
4*e27a9960SSean Young  * Copyright (C) 2005  Sean Young <sean@mess.org>
5*e27a9960SSean Young  *
6*e27a9960SSean Young  * $Id: rfd_ftl.c,v 1.4 2005/07/31 22:49:14 sean Exp $
7*e27a9960SSean Young  *
8*e27a9960SSean Young  * This type of flash translation layer (FTL) is used by the Embedded BIOS
9*e27a9960SSean Young  * by General Software. It is known as the Resident Flash Disk (RFD), see:
10*e27a9960SSean Young  *
11*e27a9960SSean Young  *	http://www.gensw.com/pages/prod/bios/rfd.htm
12*e27a9960SSean Young  *
13*e27a9960SSean Young  * based on ftl.c
14*e27a9960SSean Young  */
15*e27a9960SSean Young 
16*e27a9960SSean Young #include <linux/hdreg.h>
17*e27a9960SSean Young #include <linux/init.h>
18*e27a9960SSean Young #include <linux/mtd/blktrans.h>
19*e27a9960SSean Young #include <linux/mtd/mtd.h>
20*e27a9960SSean Young #include <linux/vmalloc.h>
21*e27a9960SSean Young 
22*e27a9960SSean Young #include <asm/types.h>
23*e27a9960SSean Young 
24*e27a9960SSean Young #define const_cpu_to_le16	__constant_cpu_to_le16
25*e27a9960SSean Young 
26*e27a9960SSean Young static int block_size = 0;
27*e27a9960SSean Young module_param(block_size, int, 0);
28*e27a9960SSean Young MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size");
29*e27a9960SSean Young 
30*e27a9960SSean Young #define PREFIX "rfd_ftl: "
31*e27a9960SSean Young 
32*e27a9960SSean Young /* Major device # for FTL device */
33*e27a9960SSean Young 
34*e27a9960SSean Young /* A request for this major has been sent to device@lanana.org */
35*e27a9960SSean Young #ifndef RFD_FTL_MAJOR
36*e27a9960SSean Young #define RFD_FTL_MAJOR		95
37*e27a9960SSean Young #endif
38*e27a9960SSean Young 
39*e27a9960SSean Young /* Maximum number of partitions in an FTL region */
40*e27a9960SSean Young #define PART_BITS		4
41*e27a9960SSean Young 
42*e27a9960SSean Young /* An erase unit should start with this value */
43*e27a9960SSean Young #define RFD_MAGIC		0x9193
44*e27a9960SSean Young 
45*e27a9960SSean Young /* the second value is 0xffff or 0xffc8; function unknown */
46*e27a9960SSean Young 
47*e27a9960SSean Young /* the third value is always 0xffff, ignored */
48*e27a9960SSean Young 
49*e27a9960SSean Young /* next is an array of mapping for each corresponding sector */
50*e27a9960SSean Young #define HEADER_MAP_OFFSET	3
51*e27a9960SSean Young #define SECTOR_DELETED		0x0000
52*e27a9960SSean Young #define SECTOR_ZERO		0xfffe
53*e27a9960SSean Young #define SECTOR_FREE		0xffff
54*e27a9960SSean Young 
55*e27a9960SSean Young #define SECTOR_SIZE		512
56*e27a9960SSean Young 
57*e27a9960SSean Young #define SECTORS_PER_TRACK	63
58*e27a9960SSean Young 
59*e27a9960SSean Young struct block {
60*e27a9960SSean Young 	enum {
61*e27a9960SSean Young 		BLOCK_OK,
62*e27a9960SSean Young 		BLOCK_ERASING,
63*e27a9960SSean Young 		BLOCK_ERASED,
64*e27a9960SSean Young 		BLOCK_FAILED
65*e27a9960SSean Young 	} state;
66*e27a9960SSean Young 	int free_sectors;
67*e27a9960SSean Young 	int used_sectors;
68*e27a9960SSean Young 	int erases;
69*e27a9960SSean Young 	u_long offset;
70*e27a9960SSean Young };
71*e27a9960SSean Young 
72*e27a9960SSean Young struct partition {
73*e27a9960SSean Young 	struct mtd_blktrans_dev mbd;
74*e27a9960SSean Young 
75*e27a9960SSean Young 	u_int block_size;		/* size of erase unit */
76*e27a9960SSean Young 	u_int total_blocks;		/* number of erase units */
77*e27a9960SSean Young 	u_int header_sectors_per_block;	/* header sectors in erase unit */
78*e27a9960SSean Young 	u_int data_sectors_per_block;	/* data sectors in erase unit */
79*e27a9960SSean Young 	u_int sector_count;		/* sectors in translated disk */
80*e27a9960SSean Young 	u_int header_size;		/* bytes in header sector */
81*e27a9960SSean Young 	int reserved_block;		/* block next up for reclaim */
82*e27a9960SSean Young 	int current_block;		/* block to write to */
83*e27a9960SSean Young 	u16 *header_cache;		/* cached header */
84*e27a9960SSean Young 
85*e27a9960SSean Young 	int is_reclaiming;
86*e27a9960SSean Young 	int cylinders;
87*e27a9960SSean Young 	int errors;
88*e27a9960SSean Young 	u_long *sector_map;
89*e27a9960SSean Young 	struct block *blocks;
90*e27a9960SSean Young };
91*e27a9960SSean Young 
92*e27a9960SSean Young static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
93*e27a9960SSean Young 
94*e27a9960SSean Young static int build_block_map(struct partition *part, int block_no)
95*e27a9960SSean Young {
96*e27a9960SSean Young 	struct block *block = &part->blocks[block_no];
97*e27a9960SSean Young 	int i;
98*e27a9960SSean Young 
99*e27a9960SSean Young 	block->offset = part->block_size * block_no;
100*e27a9960SSean Young 
101*e27a9960SSean Young 	if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) {
102*e27a9960SSean Young 		block->state = BLOCK_ERASED; /* assumption */
103*e27a9960SSean Young 		block->free_sectors = part->data_sectors_per_block;
104*e27a9960SSean Young 		part->reserved_block = block_no;
105*e27a9960SSean Young 		return 1;
106*e27a9960SSean Young 	}
107*e27a9960SSean Young 
108*e27a9960SSean Young 	block->state = BLOCK_OK;
109*e27a9960SSean Young 
110*e27a9960SSean Young 	for (i=0; i<part->data_sectors_per_block; i++) {
111*e27a9960SSean Young 		u16 entry;
112*e27a9960SSean Young 
113*e27a9960SSean Young 		entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);
114*e27a9960SSean Young 
115*e27a9960SSean Young 		if (entry == SECTOR_DELETED)
116*e27a9960SSean Young 			continue;
117*e27a9960SSean Young 
118*e27a9960SSean Young 		if (entry == SECTOR_FREE) {
119*e27a9960SSean Young 			block->free_sectors++;
120*e27a9960SSean Young 			continue;
121*e27a9960SSean Young 		}
122*e27a9960SSean Young 
123*e27a9960SSean Young 		if (entry == SECTOR_ZERO)
124*e27a9960SSean Young 			entry = 0;
125*e27a9960SSean Young 
126*e27a9960SSean Young 		if (entry >= part->sector_count) {
127*e27a9960SSean Young 			printk(KERN_NOTICE PREFIX
128*e27a9960SSean Young 				"'%s': unit #%d: entry %d corrupt, "
129*e27a9960SSean Young 				"sector %d out of range\n",
130*e27a9960SSean Young 				part->mbd.mtd->name, block_no, i, entry);
131*e27a9960SSean Young 			continue;
132*e27a9960SSean Young 		}
133*e27a9960SSean Young 
134*e27a9960SSean Young 		if (part->sector_map[entry] != -1) {
135*e27a9960SSean Young 			printk(KERN_NOTICE PREFIX
136*e27a9960SSean Young 				"'%s': more than one entry for sector %d\n",
137*e27a9960SSean Young 				part->mbd.mtd->name, entry);
138*e27a9960SSean Young 			part->errors = 1;
139*e27a9960SSean Young 			continue;
140*e27a9960SSean Young 		}
141*e27a9960SSean Young 
142*e27a9960SSean Young 		part->sector_map[entry] = block->offset +
143*e27a9960SSean Young 			(i + part->header_sectors_per_block) * SECTOR_SIZE;
144*e27a9960SSean Young 
145*e27a9960SSean Young 		block->used_sectors++;
146*e27a9960SSean Young 	}
147*e27a9960SSean Young 
148*e27a9960SSean Young 	if (block->free_sectors == part->data_sectors_per_block)
149*e27a9960SSean Young 		part->reserved_block = block_no;
150*e27a9960SSean Young 
151*e27a9960SSean Young 	return 0;
152*e27a9960SSean Young }
153*e27a9960SSean Young 
154*e27a9960SSean Young static int scan_header(struct partition *part)
155*e27a9960SSean Young {
156*e27a9960SSean Young 	int sectors_per_block;
157*e27a9960SSean Young 	int i, rc = -ENOMEM;
158*e27a9960SSean Young 	int blocks_found;
159*e27a9960SSean Young 	size_t retlen;
160*e27a9960SSean Young 
161*e27a9960SSean Young 	sectors_per_block = part->block_size / SECTOR_SIZE;
162*e27a9960SSean Young 	part->total_blocks = part->mbd.mtd->size / part->block_size;
163*e27a9960SSean Young 
164*e27a9960SSean Young 	if (part->total_blocks < 2)
165*e27a9960SSean Young 		return -ENOENT;
166*e27a9960SSean Young 
167*e27a9960SSean Young 	/* each erase block has three bytes header, followed by the map */
168*e27a9960SSean Young 	part->header_sectors_per_block =
169*e27a9960SSean Young 			((HEADER_MAP_OFFSET + sectors_per_block) *
170*e27a9960SSean Young 		 	sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE;
171*e27a9960SSean Young 
172*e27a9960SSean Young 	part->data_sectors_per_block = sectors_per_block -
173*e27a9960SSean Young 			part->header_sectors_per_block;
174*e27a9960SSean Young 
175*e27a9960SSean Young 	part->header_size = (HEADER_MAP_OFFSET +
176*e27a9960SSean Young 			part->data_sectors_per_block) * sizeof(u16);
177*e27a9960SSean Young 
178*e27a9960SSean Young 	part->cylinders = (part->data_sectors_per_block *
179*e27a9960SSean Young 			(part->total_blocks - 1) - 1) / SECTORS_PER_TRACK;
180*e27a9960SSean Young 
181*e27a9960SSean Young 	part->sector_count = part->cylinders * SECTORS_PER_TRACK;
182*e27a9960SSean Young 
183*e27a9960SSean Young 	part->current_block = -1;
184*e27a9960SSean Young 	part->reserved_block = -1;
185*e27a9960SSean Young 	part->is_reclaiming = 0;
186*e27a9960SSean Young 
187*e27a9960SSean Young 	part->header_cache = kmalloc(part->header_size, GFP_KERNEL);
188*e27a9960SSean Young 	if (!part->header_cache)
189*e27a9960SSean Young 		goto err;
190*e27a9960SSean Young 
191*e27a9960SSean Young 	part->blocks = kcalloc(part->total_blocks, sizeof(struct block),
192*e27a9960SSean Young 			GFP_KERNEL);
193*e27a9960SSean Young 	if (!part->blocks)
194*e27a9960SSean Young 		goto err;
195*e27a9960SSean Young 
196*e27a9960SSean Young 	part->sector_map = vmalloc(part->sector_count * sizeof(u_long));
197*e27a9960SSean Young 	if (!part->sector_map) {
198*e27a9960SSean Young 		printk(KERN_ERR PREFIX "'%s': unable to allocate memory for "
199*e27a9960SSean Young 			"sector map", part->mbd.mtd->name);
200*e27a9960SSean Young 		goto err;
201*e27a9960SSean Young 	}
202*e27a9960SSean Young 
203*e27a9960SSean Young 	for (i=0; i<part->sector_count; i++)
204*e27a9960SSean Young 		part->sector_map[i] = -1;
205*e27a9960SSean Young 
206*e27a9960SSean Young 	for (i=0, blocks_found=0; i<part->total_blocks; i++) {
207*e27a9960SSean Young 		rc = part->mbd.mtd->read(part->mbd.mtd,
208*e27a9960SSean Young 				i * part->block_size, part->header_size,
209*e27a9960SSean Young 				&retlen, (u_char*)part->header_cache);
210*e27a9960SSean Young 
211*e27a9960SSean Young 		if (!rc && retlen != part->header_size)
212*e27a9960SSean Young 			rc = -EIO;
213*e27a9960SSean Young 
214*e27a9960SSean Young 		if (rc)
215*e27a9960SSean Young 			goto err;
216*e27a9960SSean Young 
217*e27a9960SSean Young 		if (!build_block_map(part, i))
218*e27a9960SSean Young 			blocks_found++;
219*e27a9960SSean Young 	}
220*e27a9960SSean Young 
221*e27a9960SSean Young 	if (blocks_found == 0) {
222*e27a9960SSean Young 		printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n",
223*e27a9960SSean Young 				part->mbd.mtd->name);
224*e27a9960SSean Young 		rc = -ENOENT;
225*e27a9960SSean Young 		goto err;
226*e27a9960SSean Young 	}
227*e27a9960SSean Young 
228*e27a9960SSean Young 	if (part->reserved_block == -1) {
229*e27a9960SSean Young 		printk(KERN_NOTICE PREFIX "'%s': no empty erase unit found\n",
230*e27a9960SSean Young 				part->mbd.mtd->name);
231*e27a9960SSean Young 
232*e27a9960SSean Young 		part->errors = 1;
233*e27a9960SSean Young 	}
234*e27a9960SSean Young 
235*e27a9960SSean Young 	return 0;
236*e27a9960SSean Young 
237*e27a9960SSean Young err:
238*e27a9960SSean Young 	vfree(part->sector_map);
239*e27a9960SSean Young 	kfree(part->header_cache);
240*e27a9960SSean Young 	kfree(part->blocks);
241*e27a9960SSean Young 
242*e27a9960SSean Young 	return rc;
243*e27a9960SSean Young }
244*e27a9960SSean Young 
245*e27a9960SSean Young static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
246*e27a9960SSean Young {
247*e27a9960SSean Young 	struct partition *part = (struct partition*)dev;
248*e27a9960SSean Young 	u_long addr;
249*e27a9960SSean Young 	size_t retlen;
250*e27a9960SSean Young 	int rc;
251*e27a9960SSean Young 
252*e27a9960SSean Young 	if (sector >= part->sector_count)
253*e27a9960SSean Young 		return -EIO;
254*e27a9960SSean Young 
255*e27a9960SSean Young 	addr = part->sector_map[sector];
256*e27a9960SSean Young 	if (addr != -1) {
257*e27a9960SSean Young 		rc = part->mbd.mtd->read(part->mbd.mtd, addr, SECTOR_SIZE,
258*e27a9960SSean Young 						&retlen, (u_char*)buf);
259*e27a9960SSean Young 		if (!rc && retlen != SECTOR_SIZE)
260*e27a9960SSean Young 			rc = -EIO;
261*e27a9960SSean Young 
262*e27a9960SSean Young 		if (rc) {
263*e27a9960SSean Young 			printk(KERN_WARNING PREFIX "error reading '%s' at "
264*e27a9960SSean Young 				"0x%lx\n", part->mbd.mtd->name, addr);
265*e27a9960SSean Young 			return rc;
266*e27a9960SSean Young 		}
267*e27a9960SSean Young 	} else
268*e27a9960SSean Young 		memset(buf, 0, SECTOR_SIZE);
269*e27a9960SSean Young 
270*e27a9960SSean Young 	return 0;
271*e27a9960SSean Young }
272*e27a9960SSean Young 
273*e27a9960SSean Young static void erase_callback(struct erase_info *erase)
274*e27a9960SSean Young {
275*e27a9960SSean Young 	struct partition *part;
276*e27a9960SSean Young 	u16 magic;
277*e27a9960SSean Young 	int i, rc;
278*e27a9960SSean Young 	size_t retlen;
279*e27a9960SSean Young 
280*e27a9960SSean Young 	part = (struct partition*)erase->priv;
281*e27a9960SSean Young 
282*e27a9960SSean Young 	i = erase->addr / part->block_size;
283*e27a9960SSean Young 	if (i >= part->total_blocks || part->blocks[i].offset != erase->addr) {
284*e27a9960SSean Young 		printk(KERN_ERR PREFIX "erase callback for unknown offset %x "
285*e27a9960SSean Young 				"on '%s'\n", erase->addr, part->mbd.mtd->name);
286*e27a9960SSean Young 		return;
287*e27a9960SSean Young 	}
288*e27a9960SSean Young 
289*e27a9960SSean Young 	if (erase->state != MTD_ERASE_DONE) {
290*e27a9960SSean Young 		printk(KERN_WARNING PREFIX "erase failed at 0x%x on '%s', "
291*e27a9960SSean Young 				"state %d\n", erase->addr,
292*e27a9960SSean Young 				part->mbd.mtd->name, erase->state);
293*e27a9960SSean Young 
294*e27a9960SSean Young 		part->blocks[i].state = BLOCK_FAILED;
295*e27a9960SSean Young 		part->blocks[i].free_sectors = 0;
296*e27a9960SSean Young 		part->blocks[i].used_sectors = 0;
297*e27a9960SSean Young 
298*e27a9960SSean Young 		kfree(erase);
299*e27a9960SSean Young 
300*e27a9960SSean Young 		return;
301*e27a9960SSean Young 	}
302*e27a9960SSean Young 
303*e27a9960SSean Young 	magic = const_cpu_to_le16(RFD_MAGIC);
304*e27a9960SSean Young 
305*e27a9960SSean Young 	part->blocks[i].state = BLOCK_ERASED;
306*e27a9960SSean Young 	part->blocks[i].free_sectors = part->data_sectors_per_block;
307*e27a9960SSean Young 	part->blocks[i].used_sectors = 0;
308*e27a9960SSean Young 	part->blocks[i].erases++;
309*e27a9960SSean Young 
310*e27a9960SSean Young 	rc = part->mbd.mtd->write(part->mbd.mtd,
311*e27a9960SSean Young 		part->blocks[i].offset, sizeof(magic), &retlen,
312*e27a9960SSean Young 		(u_char*)&magic);
313*e27a9960SSean Young 
314*e27a9960SSean Young 	if (!rc && retlen != sizeof(magic))
315*e27a9960SSean Young 		rc = -EIO;
316*e27a9960SSean Young 
317*e27a9960SSean Young 	if (rc) {
318*e27a9960SSean Young 		printk(KERN_NOTICE PREFIX "'%s': unable to write RFD "
319*e27a9960SSean Young 				"header at 0x%lx\n",
320*e27a9960SSean Young 				part->mbd.mtd->name,
321*e27a9960SSean Young 				part->blocks[i].offset);
322*e27a9960SSean Young 		part->blocks[i].state = BLOCK_FAILED;
323*e27a9960SSean Young 	}
324*e27a9960SSean Young 	else
325*e27a9960SSean Young 		part->blocks[i].state = BLOCK_OK;
326*e27a9960SSean Young 
327*e27a9960SSean Young 	kfree(erase);
328*e27a9960SSean Young }
329*e27a9960SSean Young 
330*e27a9960SSean Young static int erase_block(struct partition *part, int block)
331*e27a9960SSean Young {
332*e27a9960SSean Young 	struct erase_info *erase;
333*e27a9960SSean Young 	int rc = -ENOMEM;
334*e27a9960SSean Young 
335*e27a9960SSean Young 	erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
336*e27a9960SSean Young 	if (!erase)
337*e27a9960SSean Young 		goto err;
338*e27a9960SSean Young 
339*e27a9960SSean Young 	erase->mtd = part->mbd.mtd;
340*e27a9960SSean Young 	erase->callback = erase_callback;
341*e27a9960SSean Young 	erase->addr = part->blocks[block].offset;
342*e27a9960SSean Young 	erase->len = part->block_size;
343*e27a9960SSean Young 	erase->priv = (u_long)part;
344*e27a9960SSean Young 
345*e27a9960SSean Young 	part->blocks[block].state = BLOCK_ERASING;
346*e27a9960SSean Young 	part->blocks[block].free_sectors = 0;
347*e27a9960SSean Young 
348*e27a9960SSean Young 	rc = part->mbd.mtd->erase(part->mbd.mtd, erase);
349*e27a9960SSean Young 
350*e27a9960SSean Young 	if (rc) {
351*e27a9960SSean Young 		printk(KERN_WARNING PREFIX "erase of region %x,%x on '%s' "
352*e27a9960SSean Young 				"failed\n", erase->addr, erase->len,
353*e27a9960SSean Young 				part->mbd.mtd->name);
354*e27a9960SSean Young 		kfree(erase);
355*e27a9960SSean Young 	}
356*e27a9960SSean Young 
357*e27a9960SSean Young err:
358*e27a9960SSean Young 	return rc;
359*e27a9960SSean Young }
360*e27a9960SSean Young 
361*e27a9960SSean Young static int move_block_contents(struct partition *part, int block_no, u_long *old_sector)
362*e27a9960SSean Young {
363*e27a9960SSean Young 	void *sector_data;
364*e27a9960SSean Young 	u16 *map;
365*e27a9960SSean Young 	size_t retlen;
366*e27a9960SSean Young 	int i, rc = -ENOMEM;
367*e27a9960SSean Young 
368*e27a9960SSean Young 	part->is_reclaiming = 1;
369*e27a9960SSean Young 
370*e27a9960SSean Young 	sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL);
371*e27a9960SSean Young 	if (!sector_data)
372*e27a9960SSean Young 		goto err3;
373*e27a9960SSean Young 
374*e27a9960SSean Young 	map = kmalloc(part->header_size, GFP_KERNEL);
375*e27a9960SSean Young 	if (!map)
376*e27a9960SSean Young 		goto err2;
377*e27a9960SSean Young 
378*e27a9960SSean Young 	rc = part->mbd.mtd->read(part->mbd.mtd,
379*e27a9960SSean Young 		part->blocks[block_no].offset, part->header_size,
380*e27a9960SSean Young 		&retlen, (u_char*)map);
381*e27a9960SSean Young 
382*e27a9960SSean Young 	if (!rc && retlen != part->header_size)
383*e27a9960SSean Young 		rc = -EIO;
384*e27a9960SSean Young 
385*e27a9960SSean Young 	if (rc) {
386*e27a9960SSean Young 		printk(KERN_NOTICE PREFIX "error reading '%s' at "
387*e27a9960SSean Young 			"0x%lx\n", part->mbd.mtd->name,
388*e27a9960SSean Young 			part->blocks[block_no].offset);
389*e27a9960SSean Young 
390*e27a9960SSean Young 		goto err;
391*e27a9960SSean Young 	}
392*e27a9960SSean Young 
393*e27a9960SSean Young 	for (i=0; i<part->data_sectors_per_block; i++) {
394*e27a9960SSean Young 		u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]);
395*e27a9960SSean Young 		u_long addr;
396*e27a9960SSean Young 
397*e27a9960SSean Young 
398*e27a9960SSean Young 		if (entry == SECTOR_FREE || entry == SECTOR_DELETED)
399*e27a9960SSean Young 			continue;
400*e27a9960SSean Young 
401*e27a9960SSean Young 		if (entry == SECTOR_ZERO)
402*e27a9960SSean Young 			entry = 0;
403*e27a9960SSean Young 
404*e27a9960SSean Young 		/* already warned about and ignored in build_block_map() */
405*e27a9960SSean Young 		if (entry >= part->sector_count)
406*e27a9960SSean Young 			continue;
407*e27a9960SSean Young 
408*e27a9960SSean Young 		addr = part->blocks[block_no].offset +
409*e27a9960SSean Young 			(i + part->header_sectors_per_block) * SECTOR_SIZE;
410*e27a9960SSean Young 
411*e27a9960SSean Young 		if (*old_sector == addr) {
412*e27a9960SSean Young 			*old_sector = -1;
413*e27a9960SSean Young 			if (!part->blocks[block_no].used_sectors--) {
414*e27a9960SSean Young 				rc = erase_block(part, block_no);
415*e27a9960SSean Young 				break;
416*e27a9960SSean Young 			}
417*e27a9960SSean Young 			continue;
418*e27a9960SSean Young 		}
419*e27a9960SSean Young 		rc = part->mbd.mtd->read(part->mbd.mtd, addr,
420*e27a9960SSean Young 			SECTOR_SIZE, &retlen, sector_data);
421*e27a9960SSean Young 
422*e27a9960SSean Young 		if (!rc && retlen != SECTOR_SIZE)
423*e27a9960SSean Young 			rc = -EIO;
424*e27a9960SSean Young 
425*e27a9960SSean Young 		if (rc) {
426*e27a9960SSean Young 			printk(KERN_NOTICE PREFIX "'%s': Unable to "
427*e27a9960SSean Young 				"read sector for relocation\n",
428*e27a9960SSean Young 				part->mbd.mtd->name);
429*e27a9960SSean Young 
430*e27a9960SSean Young 			goto err;
431*e27a9960SSean Young 		}
432*e27a9960SSean Young 
433*e27a9960SSean Young 		rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part,
434*e27a9960SSean Young 				entry, sector_data);
435*e27a9960SSean Young 
436*e27a9960SSean Young 		if (rc)
437*e27a9960SSean Young 			goto err;
438*e27a9960SSean Young 	}
439*e27a9960SSean Young 
440*e27a9960SSean Young err:
441*e27a9960SSean Young 	kfree(map);
442*e27a9960SSean Young err2:
443*e27a9960SSean Young 	kfree(sector_data);
444*e27a9960SSean Young err3:
445*e27a9960SSean Young 	part->is_reclaiming = 0;
446*e27a9960SSean Young 
447*e27a9960SSean Young 	return rc;
448*e27a9960SSean Young }
449*e27a9960SSean Young 
450*e27a9960SSean Young static int reclaim_block(struct partition *part, u_long *old_sector)
451*e27a9960SSean Young {
452*e27a9960SSean Young 	int block, best_block, score, old_sector_block;
453*e27a9960SSean Young 	int rc;
454*e27a9960SSean Young 
455*e27a9960SSean Young 	/* we have a race if sync doesn't exist */
456*e27a9960SSean Young 	if (part->mbd.mtd->sync)
457*e27a9960SSean Young 		part->mbd.mtd->sync(part->mbd.mtd);
458*e27a9960SSean Young 
459*e27a9960SSean Young 	score = 0x7fffffff; /* MAX_INT */
460*e27a9960SSean Young 	best_block = -1;
461*e27a9960SSean Young 	if (*old_sector != -1)
462*e27a9960SSean Young 		old_sector_block = *old_sector / part->block_size;
463*e27a9960SSean Young 	else
464*e27a9960SSean Young 		old_sector_block = -1;
465*e27a9960SSean Young 
466*e27a9960SSean Young 	for (block=0; block<part->total_blocks; block++) {
467*e27a9960SSean Young 		int this_score;
468*e27a9960SSean Young 
469*e27a9960SSean Young 		if (block == part->reserved_block)
470*e27a9960SSean Young 			continue;
471*e27a9960SSean Young 
472*e27a9960SSean Young 		/*
473*e27a9960SSean Young 		 * Postpone reclaiming if there is a free sector as
474*e27a9960SSean Young 		 * more removed sectors is more efficient (have to move
475*e27a9960SSean Young 		 * less).
476*e27a9960SSean Young 		 */
477*e27a9960SSean Young 		if (part->blocks[block].free_sectors)
478*e27a9960SSean Young 			return 0;
479*e27a9960SSean Young 
480*e27a9960SSean Young 		this_score = part->blocks[block].used_sectors;
481*e27a9960SSean Young 
482*e27a9960SSean Young 		if (block == old_sector_block)
483*e27a9960SSean Young 			this_score--;
484*e27a9960SSean Young 		else {
485*e27a9960SSean Young 			/* no point in moving a full block */
486*e27a9960SSean Young 			if (part->blocks[block].used_sectors ==
487*e27a9960SSean Young 					part->data_sectors_per_block)
488*e27a9960SSean Young 				continue;
489*e27a9960SSean Young 		}
490*e27a9960SSean Young 
491*e27a9960SSean Young 		this_score += part->blocks[block].erases;
492*e27a9960SSean Young 
493*e27a9960SSean Young 		if (this_score < score) {
494*e27a9960SSean Young 			best_block = block;
495*e27a9960SSean Young 			score = this_score;
496*e27a9960SSean Young 		}
497*e27a9960SSean Young 	}
498*e27a9960SSean Young 
499*e27a9960SSean Young 	if (best_block == -1)
500*e27a9960SSean Young 		return -ENOSPC;
501*e27a9960SSean Young 
502*e27a9960SSean Young 	part->current_block = -1;
503*e27a9960SSean Young 	part->reserved_block = best_block;
504*e27a9960SSean Young 
505*e27a9960SSean Young 	pr_debug("reclaim_block: reclaiming block #%d with %d used "
506*e27a9960SSean Young 		 "%d free sectors\n", best_block,
507*e27a9960SSean Young 		 part->blocks[best_block].used_sectors,
508*e27a9960SSean Young 		 part->blocks[best_block].free_sectors);
509*e27a9960SSean Young 
510*e27a9960SSean Young 	if (part->blocks[best_block].used_sectors)
511*e27a9960SSean Young 		rc = move_block_contents(part, best_block, old_sector);
512*e27a9960SSean Young 	else
513*e27a9960SSean Young 		rc = erase_block(part, best_block);
514*e27a9960SSean Young 
515*e27a9960SSean Young 	return rc;
516*e27a9960SSean Young }
517*e27a9960SSean Young 
518*e27a9960SSean Young /*
519*e27a9960SSean Young  * IMPROVE: It would be best to choose the block with the most deleted sectors,
520*e27a9960SSean Young  * because if we fill that one up first it'll have the most chance of having
521*e27a9960SSean Young  * the least live sectors at reclaim.
522*e27a9960SSean Young  */
523*e27a9960SSean Young static int find_free_block(const struct partition *part)
524*e27a9960SSean Young {
525*e27a9960SSean Young 	int block, stop;
526*e27a9960SSean Young 
527*e27a9960SSean Young 	block = part->current_block == -1 ?
528*e27a9960SSean Young 			jiffies % part->total_blocks : part->current_block;
529*e27a9960SSean Young 	stop = block;
530*e27a9960SSean Young 
531*e27a9960SSean Young 	do {
532*e27a9960SSean Young 		if (part->blocks[block].free_sectors &&
533*e27a9960SSean Young 				block != part->reserved_block)
534*e27a9960SSean Young 			return block;
535*e27a9960SSean Young 
536*e27a9960SSean Young 		if (++block >= part->total_blocks)
537*e27a9960SSean Young 			block = 0;
538*e27a9960SSean Young 
539*e27a9960SSean Young 	} while (block != stop);
540*e27a9960SSean Young 
541*e27a9960SSean Young 	return -1;
542*e27a9960SSean Young }
543*e27a9960SSean Young 
544*e27a9960SSean Young static int find_writeable_block(struct partition *part, u_long *old_sector)
545*e27a9960SSean Young {
546*e27a9960SSean Young 	int rc, block;
547*e27a9960SSean Young 	size_t retlen;
548*e27a9960SSean Young 
549*e27a9960SSean Young 	block = find_free_block(part);
550*e27a9960SSean Young 
551*e27a9960SSean Young 	if (block == -1) {
552*e27a9960SSean Young 		if (!part->is_reclaiming) {
553*e27a9960SSean Young 			rc = reclaim_block(part, old_sector);
554*e27a9960SSean Young 			if (rc)
555*e27a9960SSean Young 				goto err;
556*e27a9960SSean Young 
557*e27a9960SSean Young 			block = find_free_block(part);
558*e27a9960SSean Young 		}
559*e27a9960SSean Young 
560*e27a9960SSean Young 		if (block == -1) {
561*e27a9960SSean Young 			rc = -ENOSPC;
562*e27a9960SSean Young 			goto err;
563*e27a9960SSean Young 		}
564*e27a9960SSean Young 	}
565*e27a9960SSean Young 
566*e27a9960SSean Young 	rc = part->mbd.mtd->read(part->mbd.mtd, part->blocks[block].offset,
567*e27a9960SSean Young 		part->header_size, &retlen, (u_char*)part->header_cache);
568*e27a9960SSean Young 
569*e27a9960SSean Young 	if (!rc && retlen != part->header_size)
570*e27a9960SSean Young 		rc = -EIO;
571*e27a9960SSean Young 
572*e27a9960SSean Young 	if (rc) {
573*e27a9960SSean Young 		printk(KERN_NOTICE PREFIX "'%s': unable to read header at "
574*e27a9960SSean Young 				"0x%lx\n", part->mbd.mtd->name,
575*e27a9960SSean Young 				part->blocks[block].offset);
576*e27a9960SSean Young 		goto err;
577*e27a9960SSean Young 	}
578*e27a9960SSean Young 
579*e27a9960SSean Young 	part->current_block = block;
580*e27a9960SSean Young 
581*e27a9960SSean Young err:
582*e27a9960SSean Young 	return rc;
583*e27a9960SSean Young }
584*e27a9960SSean Young 
585*e27a9960SSean Young static int mark_sector_deleted(struct partition *part, u_long old_addr)
586*e27a9960SSean Young {
587*e27a9960SSean Young 	int block, offset, rc;
588*e27a9960SSean Young 	u_long addr;
589*e27a9960SSean Young 	size_t retlen;
590*e27a9960SSean Young 	u16 del = const_cpu_to_le16(SECTOR_DELETED);
591*e27a9960SSean Young 
592*e27a9960SSean Young 	block = old_addr / part->block_size;
593*e27a9960SSean Young 	offset = (old_addr % part->block_size) / SECTOR_SIZE -
594*e27a9960SSean Young 		part->header_sectors_per_block;
595*e27a9960SSean Young 
596*e27a9960SSean Young 	addr = part->blocks[block].offset +
597*e27a9960SSean Young 			(HEADER_MAP_OFFSET + offset) * sizeof(u16);
598*e27a9960SSean Young 	rc = part->mbd.mtd->write(part->mbd.mtd, addr,
599*e27a9960SSean Young 		sizeof(del), &retlen, (u_char*)&del);
600*e27a9960SSean Young 
601*e27a9960SSean Young 	if (!rc && retlen != sizeof(del))
602*e27a9960SSean Young 		rc = -EIO;
603*e27a9960SSean Young 
604*e27a9960SSean Young 	if (rc) {
605*e27a9960SSean Young 		printk(KERN_WARNING PREFIX "error writing '%s' at "
606*e27a9960SSean Young 			"0x%lx\n", part->mbd.mtd->name, addr);
607*e27a9960SSean Young 		if (rc)
608*e27a9960SSean Young 			goto err;
609*e27a9960SSean Young 	}
610*e27a9960SSean Young 	if (block == part->current_block)
611*e27a9960SSean Young 		part->header_cache[offset + HEADER_MAP_OFFSET] = del;
612*e27a9960SSean Young 
613*e27a9960SSean Young 	part->blocks[block].used_sectors--;
614*e27a9960SSean Young 
615*e27a9960SSean Young 	if (!part->blocks[block].used_sectors &&
616*e27a9960SSean Young 	    !part->blocks[block].free_sectors)
617*e27a9960SSean Young 		rc = erase_block(part, block);
618*e27a9960SSean Young 
619*e27a9960SSean Young err:
620*e27a9960SSean Young 	return rc;
621*e27a9960SSean Young }
622*e27a9960SSean Young 
623*e27a9960SSean Young static int find_free_sector(const struct partition *part, const struct block *block)
624*e27a9960SSean Young {
625*e27a9960SSean Young 	int i, stop;
626*e27a9960SSean Young 
627*e27a9960SSean Young 	i = stop = part->data_sectors_per_block - block->free_sectors;
628*e27a9960SSean Young 
629*e27a9960SSean Young 	do {
630*e27a9960SSean Young 		if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
631*e27a9960SSean Young 				== SECTOR_FREE)
632*e27a9960SSean Young 			return i;
633*e27a9960SSean Young 
634*e27a9960SSean Young 		if (++i == part->data_sectors_per_block)
635*e27a9960SSean Young 			i = 0;
636*e27a9960SSean Young 	}
637*e27a9960SSean Young 	while(i != stop);
638*e27a9960SSean Young 
639*e27a9960SSean Young 	return -1;
640*e27a9960SSean Young }
641*e27a9960SSean Young 
642*e27a9960SSean Young static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr)
643*e27a9960SSean Young {
644*e27a9960SSean Young 	struct partition *part = (struct partition*)dev;
645*e27a9960SSean Young 	struct block *block;
646*e27a9960SSean Young 	u_long addr;
647*e27a9960SSean Young 	int i;
648*e27a9960SSean Young 	int rc;
649*e27a9960SSean Young 	size_t retlen;
650*e27a9960SSean Young 	u16 entry;
651*e27a9960SSean Young 
652*e27a9960SSean Young 	if (part->current_block == -1 ||
653*e27a9960SSean Young 		!part->blocks[part->current_block].free_sectors) {
654*e27a9960SSean Young 
655*e27a9960SSean Young 		rc = find_writeable_block(part, old_addr);
656*e27a9960SSean Young 		if (rc)
657*e27a9960SSean Young 			goto err;
658*e27a9960SSean Young 	}
659*e27a9960SSean Young 
660*e27a9960SSean Young 	block = &part->blocks[part->current_block];
661*e27a9960SSean Young 
662*e27a9960SSean Young 	i = find_free_sector(part, block);
663*e27a9960SSean Young 
664*e27a9960SSean Young 	if (i < 0) {
665*e27a9960SSean Young 		rc = -ENOSPC;
666*e27a9960SSean Young 		goto err;
667*e27a9960SSean Young 	}
668*e27a9960SSean Young 
669*e27a9960SSean Young 	addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
670*e27a9960SSean Young 		block->offset;
671*e27a9960SSean Young 	rc = part->mbd.mtd->write(part->mbd.mtd,
672*e27a9960SSean Young 		addr, SECTOR_SIZE, &retlen, (u_char*)buf);
673*e27a9960SSean Young 
674*e27a9960SSean Young 	if (!rc && retlen != SECTOR_SIZE)
675*e27a9960SSean Young 		rc = -EIO;
676*e27a9960SSean Young 
677*e27a9960SSean Young 	if (rc) {
678*e27a9960SSean Young 		printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
679*e27a9960SSean Young 				part->mbd.mtd->name, addr);
680*e27a9960SSean Young 		if (rc)
681*e27a9960SSean Young 			goto err;
682*e27a9960SSean Young 	}
683*e27a9960SSean Young 
684*e27a9960SSean Young 	part->sector_map[sector] = addr;
685*e27a9960SSean Young 
686*e27a9960SSean Young 	entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);
687*e27a9960SSean Young 
688*e27a9960SSean Young 	part->header_cache[i + HEADER_MAP_OFFSET] = entry;
689*e27a9960SSean Young 
690*e27a9960SSean Young 	addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16);
691*e27a9960SSean Young 	rc = part->mbd.mtd->write(part->mbd.mtd, addr,
692*e27a9960SSean Young 			sizeof(entry), &retlen, (u_char*)&entry);
693*e27a9960SSean Young 
694*e27a9960SSean Young 	if (!rc && retlen != sizeof(entry))
695*e27a9960SSean Young 		rc = -EIO;
696*e27a9960SSean Young 
697*e27a9960SSean Young 	if (rc) {
698*e27a9960SSean Young 		printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n",
699*e27a9960SSean Young 				part->mbd.mtd->name, addr);
700*e27a9960SSean Young 		if (rc)
701*e27a9960SSean Young 			goto err;
702*e27a9960SSean Young 	}
703*e27a9960SSean Young 	block->used_sectors++;
704*e27a9960SSean Young 	block->free_sectors--;
705*e27a9960SSean Young 
706*e27a9960SSean Young err:
707*e27a9960SSean Young 	return rc;
708*e27a9960SSean Young }
709*e27a9960SSean Young 
710*e27a9960SSean Young static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
711*e27a9960SSean Young {
712*e27a9960SSean Young 	struct partition *part = (struct partition*)dev;
713*e27a9960SSean Young 	u_long old_addr;
714*e27a9960SSean Young 	int i;
715*e27a9960SSean Young 	int rc = 0;
716*e27a9960SSean Young 
717*e27a9960SSean Young 	pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector);
718*e27a9960SSean Young 
719*e27a9960SSean Young 	if (part->reserved_block == -1) {
720*e27a9960SSean Young 		rc = -EACCES;
721*e27a9960SSean Young 		goto err;
722*e27a9960SSean Young 	}
723*e27a9960SSean Young 
724*e27a9960SSean Young 	if (sector >= part->sector_count) {
725*e27a9960SSean Young 		rc = -EIO;
726*e27a9960SSean Young 		goto err;
727*e27a9960SSean Young 	}
728*e27a9960SSean Young 
729*e27a9960SSean Young 	old_addr = part->sector_map[sector];
730*e27a9960SSean Young 
731*e27a9960SSean Young 	for (i=0; i<SECTOR_SIZE; i++) {
732*e27a9960SSean Young 		if (!buf[i])
733*e27a9960SSean Young 			continue;
734*e27a9960SSean Young 
735*e27a9960SSean Young 		rc = do_writesect(dev, sector, buf, &old_addr);
736*e27a9960SSean Young 		if (rc)
737*e27a9960SSean Young 			goto err;
738*e27a9960SSean Young 		break;
739*e27a9960SSean Young 	}
740*e27a9960SSean Young 
741*e27a9960SSean Young 	if (i == SECTOR_SIZE)
742*e27a9960SSean Young 		part->sector_map[sector] = -1;
743*e27a9960SSean Young 
744*e27a9960SSean Young 	if (old_addr != -1)
745*e27a9960SSean Young 		rc = mark_sector_deleted(part, old_addr);
746*e27a9960SSean Young 
747*e27a9960SSean Young err:
748*e27a9960SSean Young 	return rc;
749*e27a9960SSean Young }
750*e27a9960SSean Young 
751*e27a9960SSean Young static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
752*e27a9960SSean Young {
753*e27a9960SSean Young 	struct partition *part = (struct partition*)dev;
754*e27a9960SSean Young 
755*e27a9960SSean Young 	geo->heads = 1;
756*e27a9960SSean Young 	geo->sectors = SECTORS_PER_TRACK;
757*e27a9960SSean Young 	geo->cylinders = part->cylinders;
758*e27a9960SSean Young 
759*e27a9960SSean Young 	return 0;
760*e27a9960SSean Young }
761*e27a9960SSean Young 
762*e27a9960SSean Young static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
763*e27a9960SSean Young {
764*e27a9960SSean Young 	struct partition *part;
765*e27a9960SSean Young 
766*e27a9960SSean Young 	if (mtd->type != MTD_NORFLASH)
767*e27a9960SSean Young 		return;
768*e27a9960SSean Young 
769*e27a9960SSean Young 	part = kcalloc(1, sizeof(struct partition), GFP_KERNEL);
770*e27a9960SSean Young 	if (!part)
771*e27a9960SSean Young 		return;
772*e27a9960SSean Young 
773*e27a9960SSean Young 	part->mbd.mtd = mtd;
774*e27a9960SSean Young 
775*e27a9960SSean Young 	if (block_size)
776*e27a9960SSean Young 		part->block_size = block_size;
777*e27a9960SSean Young 	else {
778*e27a9960SSean Young 		if (!mtd->erasesize) {
779*e27a9960SSean Young 			printk(KERN_NOTICE PREFIX "please provide block_size");
780*e27a9960SSean Young 			return;
781*e27a9960SSean Young 		}
782*e27a9960SSean Young 		else
783*e27a9960SSean Young 			part->block_size = mtd->erasesize;
784*e27a9960SSean Young 	}
785*e27a9960SSean Young 
786*e27a9960SSean Young 	if (scan_header(part) == 0) {
787*e27a9960SSean Young 		part->mbd.size = part->sector_count;
788*e27a9960SSean Young 		part->mbd.blksize = SECTOR_SIZE;
789*e27a9960SSean Young 		part->mbd.tr = tr;
790*e27a9960SSean Young 		part->mbd.devnum = -1;
791*e27a9960SSean Young 		if (!(mtd->flags & MTD_WRITEABLE))
792*e27a9960SSean Young 			part->mbd.readonly = 1;
793*e27a9960SSean Young 		else if (part->errors) {
794*e27a9960SSean Young 			printk(KERN_NOTICE PREFIX "'%s': errors found, "
795*e27a9960SSean Young 					"setting read-only", mtd->name);
796*e27a9960SSean Young 			part->mbd.readonly = 1;
797*e27a9960SSean Young 		}
798*e27a9960SSean Young 
799*e27a9960SSean Young 		printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n",
800*e27a9960SSean Young 				mtd->name, mtd->type, mtd->flags);
801*e27a9960SSean Young 
802*e27a9960SSean Young 		if (!add_mtd_blktrans_dev((void*)part))
803*e27a9960SSean Young 			return;
804*e27a9960SSean Young 	}
805*e27a9960SSean Young 
806*e27a9960SSean Young 	kfree(part);
807*e27a9960SSean Young }
808*e27a9960SSean Young 
809*e27a9960SSean Young static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
810*e27a9960SSean Young {
811*e27a9960SSean Young 	struct partition *part = (struct partition*)dev;
812*e27a9960SSean Young 	int i;
813*e27a9960SSean Young 
814*e27a9960SSean Young 	for (i=0; i<part->total_blocks; i++) {
815*e27a9960SSean Young 		pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n",
816*e27a9960SSean Young 			part->mbd.mtd->name, i, part->blocks[i].erases);
817*e27a9960SSean Young 	}
818*e27a9960SSean Young 
819*e27a9960SSean Young 	del_mtd_blktrans_dev(dev);
820*e27a9960SSean Young 	vfree(part->sector_map);
821*e27a9960SSean Young 	kfree(part->header_cache);
822*e27a9960SSean Young 	kfree(part->blocks);
823*e27a9960SSean Young 	kfree(part);
824*e27a9960SSean Young }
825*e27a9960SSean Young 
826*e27a9960SSean Young struct mtd_blktrans_ops rfd_ftl_tr = {
827*e27a9960SSean Young 	.name		= "rfd",
828*e27a9960SSean Young 	.major		= RFD_FTL_MAJOR,
829*e27a9960SSean Young 	.part_bits	= PART_BITS,
830*e27a9960SSean Young 	.readsect	= rfd_ftl_readsect,
831*e27a9960SSean Young 	.writesect	= rfd_ftl_writesect,
832*e27a9960SSean Young 	.getgeo		= rfd_ftl_getgeo,
833*e27a9960SSean Young 	.add_mtd	= rfd_ftl_add_mtd,
834*e27a9960SSean Young 	.remove_dev	= rfd_ftl_remove_dev,
835*e27a9960SSean Young 	.owner		= THIS_MODULE,
836*e27a9960SSean Young };
837*e27a9960SSean Young 
838*e27a9960SSean Young static int __init init_rfd_ftl(void)
839*e27a9960SSean Young {
840*e27a9960SSean Young 	return register_mtd_blktrans(&rfd_ftl_tr);
841*e27a9960SSean Young }
842*e27a9960SSean Young 
843*e27a9960SSean Young static void __exit cleanup_rfd_ftl(void)
844*e27a9960SSean Young {
845*e27a9960SSean Young 	deregister_mtd_blktrans(&rfd_ftl_tr);
846*e27a9960SSean Young }
847*e27a9960SSean Young 
848*e27a9960SSean Young module_init(init_rfd_ftl);
849*e27a9960SSean Young module_exit(cleanup_rfd_ftl);
850*e27a9960SSean Young 
851*e27a9960SSean Young MODULE_LICENSE("GPL");
852*e27a9960SSean Young MODULE_AUTHOR("Sean Young <sean@mess.org>");
853*e27a9960SSean Young MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, "
854*e27a9960SSean Young 		"used by General Software's Embedded BIOS");
855*e27a9960SSean Young 
856