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