0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015 #include <linux/hdreg.h>
0016 #include <linux/init.h>
0017 #include <linux/mtd/blktrans.h>
0018 #include <linux/mtd/mtd.h>
0019 #include <linux/vmalloc.h>
0020 #include <linux/slab.h>
0021 #include <linux/jiffies.h>
0022 #include <linux/module.h>
0023
0024 #include <asm/types.h>
0025
0026 static int block_size = 0;
0027 module_param(block_size, int, 0);
0028 MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size");
0029
0030 #define PREFIX "rfd_ftl: "
0031
0032
0033 #ifndef RFD_FTL_MAJOR
0034 #define RFD_FTL_MAJOR 256
0035 #endif
0036
0037
0038 #define PART_BITS 4
0039
0040
0041 #define RFD_MAGIC 0x9193
0042
0043
0044
0045
0046
0047
0048 #define HEADER_MAP_OFFSET 3
0049 #define SECTOR_DELETED 0x0000
0050 #define SECTOR_ZERO 0xfffe
0051 #define SECTOR_FREE 0xffff
0052
0053 #define SECTOR_SIZE 512
0054
0055 #define SECTORS_PER_TRACK 63
0056
0057 struct block {
0058 enum {
0059 BLOCK_OK,
0060 BLOCK_ERASING,
0061 BLOCK_ERASED,
0062 BLOCK_UNUSED,
0063 BLOCK_FAILED
0064 } state;
0065 int free_sectors;
0066 int used_sectors;
0067 int erases;
0068 u_long offset;
0069 };
0070
0071 struct partition {
0072 struct mtd_blktrans_dev mbd;
0073
0074 u_int block_size;
0075 u_int total_blocks;
0076 u_int header_sectors_per_block;
0077 u_int data_sectors_per_block;
0078 u_int sector_count;
0079 u_int header_size;
0080 int reserved_block;
0081 int current_block;
0082 u16 *header_cache;
0083
0084 int is_reclaiming;
0085 int cylinders;
0086 int errors;
0087 u_long *sector_map;
0088 struct block *blocks;
0089 };
0090
0091 static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);
0092
0093 static int build_block_map(struct partition *part, int block_no)
0094 {
0095 struct block *block = &part->blocks[block_no];
0096 int i;
0097
0098 block->offset = part->block_size * block_no;
0099
0100 if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) {
0101 block->state = BLOCK_UNUSED;
0102 return -ENOENT;
0103 }
0104
0105 block->state = BLOCK_OK;
0106
0107 for (i=0; i<part->data_sectors_per_block; i++) {
0108 u16 entry;
0109
0110 entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);
0111
0112 if (entry == SECTOR_DELETED)
0113 continue;
0114
0115 if (entry == SECTOR_FREE) {
0116 block->free_sectors++;
0117 continue;
0118 }
0119
0120 if (entry == SECTOR_ZERO)
0121 entry = 0;
0122
0123 if (entry >= part->sector_count) {
0124 printk(KERN_WARNING PREFIX
0125 "'%s': unit #%d: entry %d corrupt, "
0126 "sector %d out of range\n",
0127 part->mbd.mtd->name, block_no, i, entry);
0128 continue;
0129 }
0130
0131 if (part->sector_map[entry] != -1) {
0132 printk(KERN_WARNING PREFIX
0133 "'%s': more than one entry for sector %d\n",
0134 part->mbd.mtd->name, entry);
0135 part->errors = 1;
0136 continue;
0137 }
0138
0139 part->sector_map[entry] = block->offset +
0140 (i + part->header_sectors_per_block) * SECTOR_SIZE;
0141
0142 block->used_sectors++;
0143 }
0144
0145 if (block->free_sectors == part->data_sectors_per_block)
0146 part->reserved_block = block_no;
0147
0148 return 0;
0149 }
0150
0151 static int scan_header(struct partition *part)
0152 {
0153 int sectors_per_block;
0154 int i, rc = -ENOMEM;
0155 int blocks_found;
0156 size_t retlen;
0157
0158 sectors_per_block = part->block_size / SECTOR_SIZE;
0159 part->total_blocks = (u32)part->mbd.mtd->size / part->block_size;
0160
0161 if (part->total_blocks < 2)
0162 return -ENOENT;
0163
0164
0165 part->header_sectors_per_block =
0166 ((HEADER_MAP_OFFSET + sectors_per_block) *
0167 sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE;
0168
0169 part->data_sectors_per_block = sectors_per_block -
0170 part->header_sectors_per_block;
0171
0172 part->header_size = (HEADER_MAP_OFFSET +
0173 part->data_sectors_per_block) * sizeof(u16);
0174
0175 part->cylinders = (part->data_sectors_per_block *
0176 (part->total_blocks - 1) - 1) / SECTORS_PER_TRACK;
0177
0178 part->sector_count = part->cylinders * SECTORS_PER_TRACK;
0179
0180 part->current_block = -1;
0181 part->reserved_block = -1;
0182 part->is_reclaiming = 0;
0183
0184 part->header_cache = kmalloc(part->header_size, GFP_KERNEL);
0185 if (!part->header_cache)
0186 goto err;
0187
0188 part->blocks = kcalloc(part->total_blocks, sizeof(struct block),
0189 GFP_KERNEL);
0190 if (!part->blocks)
0191 goto err;
0192
0193 part->sector_map = vmalloc(array_size(sizeof(u_long),
0194 part->sector_count));
0195 if (!part->sector_map)
0196 goto err;
0197
0198 for (i=0; i<part->sector_count; i++)
0199 part->sector_map[i] = -1;
0200
0201 for (i=0, blocks_found=0; i<part->total_blocks; i++) {
0202 rc = mtd_read(part->mbd.mtd, i * part->block_size,
0203 part->header_size, &retlen,
0204 (u_char *)part->header_cache);
0205
0206 if (!rc && retlen != part->header_size)
0207 rc = -EIO;
0208
0209 if (rc)
0210 goto err;
0211
0212 if (!build_block_map(part, i))
0213 blocks_found++;
0214 }
0215
0216 if (blocks_found == 0) {
0217 printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n",
0218 part->mbd.mtd->name);
0219 rc = -ENOENT;
0220 goto err;
0221 }
0222
0223 if (part->reserved_block == -1) {
0224 printk(KERN_WARNING PREFIX "'%s': no empty erase unit found\n",
0225 part->mbd.mtd->name);
0226
0227 part->errors = 1;
0228 }
0229
0230 return 0;
0231
0232 err:
0233 vfree(part->sector_map);
0234 kfree(part->header_cache);
0235 kfree(part->blocks);
0236
0237 return rc;
0238 }
0239
0240 static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
0241 {
0242 struct partition *part = container_of(dev, struct partition, mbd);
0243 u_long addr;
0244 size_t retlen;
0245 int rc;
0246
0247 if (sector >= part->sector_count)
0248 return -EIO;
0249
0250 addr = part->sector_map[sector];
0251 if (addr != -1) {
0252 rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,
0253 (u_char *)buf);
0254 if (!rc && retlen != SECTOR_SIZE)
0255 rc = -EIO;
0256
0257 if (rc) {
0258 printk(KERN_WARNING PREFIX "error reading '%s' at "
0259 "0x%lx\n", part->mbd.mtd->name, addr);
0260 return rc;
0261 }
0262 } else
0263 memset(buf, 0, SECTOR_SIZE);
0264
0265 return 0;
0266 }
0267
0268 static int erase_block(struct partition *part, int block)
0269 {
0270 struct erase_info *erase;
0271 int rc;
0272
0273 erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
0274 if (!erase)
0275 return -ENOMEM;
0276
0277 erase->addr = part->blocks[block].offset;
0278 erase->len = part->block_size;
0279
0280 part->blocks[block].state = BLOCK_ERASING;
0281 part->blocks[block].free_sectors = 0;
0282
0283 rc = mtd_erase(part->mbd.mtd, erase);
0284 if (rc) {
0285 printk(KERN_ERR PREFIX "erase of region %llx,%llx on '%s' "
0286 "failed\n", (unsigned long long)erase->addr,
0287 (unsigned long long)erase->len, part->mbd.mtd->name);
0288 part->blocks[block].state = BLOCK_FAILED;
0289 part->blocks[block].free_sectors = 0;
0290 part->blocks[block].used_sectors = 0;
0291 } else {
0292 u16 magic = cpu_to_le16(RFD_MAGIC);
0293 size_t retlen;
0294
0295 part->blocks[block].state = BLOCK_ERASED;
0296 part->blocks[block].free_sectors = part->data_sectors_per_block;
0297 part->blocks[block].used_sectors = 0;
0298 part->blocks[block].erases++;
0299
0300 rc = mtd_write(part->mbd.mtd, part->blocks[block].offset,
0301 sizeof(magic), &retlen, (u_char *)&magic);
0302 if (!rc && retlen != sizeof(magic))
0303 rc = -EIO;
0304
0305 if (rc) {
0306 pr_err(PREFIX "'%s': unable to write RFD header at 0x%lx\n",
0307 part->mbd.mtd->name, part->blocks[block].offset);
0308 part->blocks[block].state = BLOCK_FAILED;
0309 } else {
0310 part->blocks[block].state = BLOCK_OK;
0311 }
0312 }
0313
0314 kfree(erase);
0315
0316 return rc;
0317 }
0318
0319 static int move_block_contents(struct partition *part, int block_no, u_long *old_sector)
0320 {
0321 void *sector_data;
0322 u16 *map;
0323 size_t retlen;
0324 int i, rc = -ENOMEM;
0325
0326 part->is_reclaiming = 1;
0327
0328 sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL);
0329 if (!sector_data)
0330 goto err3;
0331
0332 map = kmalloc(part->header_size, GFP_KERNEL);
0333 if (!map)
0334 goto err2;
0335
0336 rc = mtd_read(part->mbd.mtd, part->blocks[block_no].offset,
0337 part->header_size, &retlen, (u_char *)map);
0338
0339 if (!rc && retlen != part->header_size)
0340 rc = -EIO;
0341
0342 if (rc) {
0343 printk(KERN_ERR PREFIX "error reading '%s' at "
0344 "0x%lx\n", part->mbd.mtd->name,
0345 part->blocks[block_no].offset);
0346
0347 goto err;
0348 }
0349
0350 for (i=0; i<part->data_sectors_per_block; i++) {
0351 u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]);
0352 u_long addr;
0353
0354
0355 if (entry == SECTOR_FREE || entry == SECTOR_DELETED)
0356 continue;
0357
0358 if (entry == SECTOR_ZERO)
0359 entry = 0;
0360
0361
0362 if (entry >= part->sector_count)
0363 continue;
0364
0365 addr = part->blocks[block_no].offset +
0366 (i + part->header_sectors_per_block) * SECTOR_SIZE;
0367
0368 if (*old_sector == addr) {
0369 *old_sector = -1;
0370 if (!part->blocks[block_no].used_sectors--) {
0371 rc = erase_block(part, block_no);
0372 break;
0373 }
0374 continue;
0375 }
0376 rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,
0377 sector_data);
0378
0379 if (!rc && retlen != SECTOR_SIZE)
0380 rc = -EIO;
0381
0382 if (rc) {
0383 printk(KERN_ERR PREFIX "'%s': Unable to "
0384 "read sector for relocation\n",
0385 part->mbd.mtd->name);
0386
0387 goto err;
0388 }
0389
0390 rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part,
0391 entry, sector_data);
0392
0393 if (rc)
0394 goto err;
0395 }
0396
0397 err:
0398 kfree(map);
0399 err2:
0400 kfree(sector_data);
0401 err3:
0402 part->is_reclaiming = 0;
0403
0404 return rc;
0405 }
0406
0407 static int reclaim_block(struct partition *part, u_long *old_sector)
0408 {
0409 int block, best_block, score, old_sector_block;
0410 int rc;
0411
0412
0413 mtd_sync(part->mbd.mtd);
0414
0415 score = 0x7fffffff;
0416 best_block = -1;
0417 if (*old_sector != -1)
0418 old_sector_block = *old_sector / part->block_size;
0419 else
0420 old_sector_block = -1;
0421
0422 for (block=0; block<part->total_blocks; block++) {
0423 int this_score;
0424
0425 if (block == part->reserved_block)
0426 continue;
0427
0428
0429
0430
0431
0432
0433 if (part->blocks[block].free_sectors)
0434 return 0;
0435
0436 this_score = part->blocks[block].used_sectors;
0437
0438 if (block == old_sector_block)
0439 this_score--;
0440 else {
0441
0442 if (part->blocks[block].used_sectors ==
0443 part->data_sectors_per_block)
0444 continue;
0445 }
0446
0447 this_score += part->blocks[block].erases;
0448
0449 if (this_score < score) {
0450 best_block = block;
0451 score = this_score;
0452 }
0453 }
0454
0455 if (best_block == -1)
0456 return -ENOSPC;
0457
0458 part->current_block = -1;
0459 part->reserved_block = best_block;
0460
0461 pr_debug("reclaim_block: reclaiming block #%d with %d used "
0462 "%d free sectors\n", best_block,
0463 part->blocks[best_block].used_sectors,
0464 part->blocks[best_block].free_sectors);
0465
0466 if (part->blocks[best_block].used_sectors)
0467 rc = move_block_contents(part, best_block, old_sector);
0468 else
0469 rc = erase_block(part, best_block);
0470
0471 return rc;
0472 }
0473
0474
0475
0476
0477
0478
0479 static int find_free_block(struct partition *part)
0480 {
0481 int block, stop;
0482
0483 block = part->current_block == -1 ?
0484 jiffies % part->total_blocks : part->current_block;
0485 stop = block;
0486
0487 do {
0488 if (part->blocks[block].free_sectors &&
0489 block != part->reserved_block)
0490 return block;
0491
0492 if (part->blocks[block].state == BLOCK_UNUSED)
0493 erase_block(part, block);
0494
0495 if (++block >= part->total_blocks)
0496 block = 0;
0497
0498 } while (block != stop);
0499
0500 return -1;
0501 }
0502
0503 static int find_writable_block(struct partition *part, u_long *old_sector)
0504 {
0505 int rc, block;
0506 size_t retlen;
0507
0508 block = find_free_block(part);
0509
0510 if (block == -1) {
0511 if (!part->is_reclaiming) {
0512 rc = reclaim_block(part, old_sector);
0513 if (rc)
0514 goto err;
0515
0516 block = find_free_block(part);
0517 }
0518
0519 if (block == -1) {
0520 rc = -ENOSPC;
0521 goto err;
0522 }
0523 }
0524
0525 rc = mtd_read(part->mbd.mtd, part->blocks[block].offset,
0526 part->header_size, &retlen,
0527 (u_char *)part->header_cache);
0528
0529 if (!rc && retlen != part->header_size)
0530 rc = -EIO;
0531
0532 if (rc) {
0533 printk(KERN_ERR PREFIX "'%s': unable to read header at "
0534 "0x%lx\n", part->mbd.mtd->name,
0535 part->blocks[block].offset);
0536 goto err;
0537 }
0538
0539 part->current_block = block;
0540
0541 err:
0542 return rc;
0543 }
0544
0545 static int mark_sector_deleted(struct partition *part, u_long old_addr)
0546 {
0547 int block, offset, rc;
0548 u_long addr;
0549 size_t retlen;
0550 u16 del = cpu_to_le16(SECTOR_DELETED);
0551
0552 block = old_addr / part->block_size;
0553 offset = (old_addr % part->block_size) / SECTOR_SIZE -
0554 part->header_sectors_per_block;
0555
0556 addr = part->blocks[block].offset +
0557 (HEADER_MAP_OFFSET + offset) * sizeof(u16);
0558 rc = mtd_write(part->mbd.mtd, addr, sizeof(del), &retlen,
0559 (u_char *)&del);
0560
0561 if (!rc && retlen != sizeof(del))
0562 rc = -EIO;
0563
0564 if (rc) {
0565 printk(KERN_ERR PREFIX "error writing '%s' at "
0566 "0x%lx\n", part->mbd.mtd->name, addr);
0567 goto err;
0568 }
0569 if (block == part->current_block)
0570 part->header_cache[offset + HEADER_MAP_OFFSET] = del;
0571
0572 part->blocks[block].used_sectors--;
0573
0574 if (!part->blocks[block].used_sectors &&
0575 !part->blocks[block].free_sectors)
0576 rc = erase_block(part, block);
0577
0578 err:
0579 return rc;
0580 }
0581
0582 static int find_free_sector(const struct partition *part, const struct block *block)
0583 {
0584 int i, stop;
0585
0586 i = stop = part->data_sectors_per_block - block->free_sectors;
0587
0588 do {
0589 if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])
0590 == SECTOR_FREE)
0591 return i;
0592
0593 if (++i == part->data_sectors_per_block)
0594 i = 0;
0595 }
0596 while(i != stop);
0597
0598 return -1;
0599 }
0600
0601 static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr)
0602 {
0603 struct partition *part = container_of(dev, struct partition, mbd);
0604 struct block *block;
0605 u_long addr;
0606 int i;
0607 int rc;
0608 size_t retlen;
0609 u16 entry;
0610
0611 if (part->current_block == -1 ||
0612 !part->blocks[part->current_block].free_sectors) {
0613
0614 rc = find_writable_block(part, old_addr);
0615 if (rc)
0616 goto err;
0617 }
0618
0619 block = &part->blocks[part->current_block];
0620
0621 i = find_free_sector(part, block);
0622
0623 if (i < 0) {
0624 rc = -ENOSPC;
0625 goto err;
0626 }
0627
0628 addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +
0629 block->offset;
0630 rc = mtd_write(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,
0631 (u_char *)buf);
0632
0633 if (!rc && retlen != SECTOR_SIZE)
0634 rc = -EIO;
0635
0636 if (rc) {
0637 printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n",
0638 part->mbd.mtd->name, addr);
0639 goto err;
0640 }
0641
0642 part->sector_map[sector] = addr;
0643
0644 entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);
0645
0646 part->header_cache[i + HEADER_MAP_OFFSET] = entry;
0647
0648 addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16);
0649 rc = mtd_write(part->mbd.mtd, addr, sizeof(entry), &retlen,
0650 (u_char *)&entry);
0651
0652 if (!rc && retlen != sizeof(entry))
0653 rc = -EIO;
0654
0655 if (rc) {
0656 printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n",
0657 part->mbd.mtd->name, addr);
0658 goto err;
0659 }
0660 block->used_sectors++;
0661 block->free_sectors--;
0662
0663 err:
0664 return rc;
0665 }
0666
0667 static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)
0668 {
0669 struct partition *part = container_of(dev, struct partition, mbd);
0670 u_long old_addr;
0671 int i;
0672 int rc = 0;
0673
0674 pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector);
0675
0676 if (part->reserved_block == -1) {
0677 rc = -EACCES;
0678 goto err;
0679 }
0680
0681 if (sector >= part->sector_count) {
0682 rc = -EIO;
0683 goto err;
0684 }
0685
0686 old_addr = part->sector_map[sector];
0687
0688 for (i=0; i<SECTOR_SIZE; i++) {
0689 if (!buf[i])
0690 continue;
0691
0692 rc = do_writesect(dev, sector, buf, &old_addr);
0693 if (rc)
0694 goto err;
0695 break;
0696 }
0697
0698 if (i == SECTOR_SIZE)
0699 part->sector_map[sector] = -1;
0700
0701 if (old_addr != -1)
0702 rc = mark_sector_deleted(part, old_addr);
0703
0704 err:
0705 return rc;
0706 }
0707
0708 static int rfd_ftl_discardsect(struct mtd_blktrans_dev *dev,
0709 unsigned long sector, unsigned int nr_sects)
0710 {
0711 struct partition *part = container_of(dev, struct partition, mbd);
0712 u_long addr;
0713 int rc;
0714
0715 while (nr_sects) {
0716 if (sector >= part->sector_count)
0717 return -EIO;
0718
0719 addr = part->sector_map[sector];
0720
0721 if (addr != -1) {
0722 rc = mark_sector_deleted(part, addr);
0723 if (rc)
0724 return rc;
0725
0726 part->sector_map[sector] = -1;
0727 }
0728
0729 sector++;
0730 nr_sects--;
0731 }
0732
0733 return 0;
0734 }
0735
0736 static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
0737 {
0738 struct partition *part = container_of(dev, struct partition, mbd);
0739
0740 geo->heads = 1;
0741 geo->sectors = SECTORS_PER_TRACK;
0742 geo->cylinders = part->cylinders;
0743
0744 return 0;
0745 }
0746
0747 static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
0748 {
0749 struct partition *part;
0750
0751 if ((mtd->type != MTD_NORFLASH && mtd->type != MTD_RAM) ||
0752 mtd->size > UINT_MAX)
0753 return;
0754
0755 part = kzalloc(sizeof(struct partition), GFP_KERNEL);
0756 if (!part)
0757 return;
0758
0759 part->mbd.mtd = mtd;
0760
0761 if (block_size)
0762 part->block_size = block_size;
0763 else {
0764 if (!mtd->erasesize) {
0765 printk(KERN_WARNING PREFIX "please provide block_size");
0766 goto out;
0767 } else
0768 part->block_size = mtd->erasesize;
0769 }
0770
0771 if (scan_header(part) == 0) {
0772 part->mbd.size = part->sector_count;
0773 part->mbd.tr = tr;
0774 part->mbd.devnum = -1;
0775 if (!(mtd->flags & MTD_WRITEABLE))
0776 part->mbd.readonly = 1;
0777 else if (part->errors) {
0778 printk(KERN_WARNING PREFIX "'%s': errors found, "
0779 "setting read-only\n", mtd->name);
0780 part->mbd.readonly = 1;
0781 }
0782
0783 printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n",
0784 mtd->name, mtd->type, mtd->flags);
0785
0786 if (!add_mtd_blktrans_dev(&part->mbd))
0787 return;
0788 }
0789 out:
0790 kfree(part);
0791 }
0792
0793 static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
0794 {
0795 struct partition *part = container_of(dev, struct partition, mbd);
0796 int i;
0797
0798 for (i=0; i<part->total_blocks; i++) {
0799 pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n",
0800 part->mbd.mtd->name, i, part->blocks[i].erases);
0801 }
0802
0803 vfree(part->sector_map);
0804 kfree(part->header_cache);
0805 kfree(part->blocks);
0806 del_mtd_blktrans_dev(&part->mbd);
0807 }
0808
0809 static struct mtd_blktrans_ops rfd_ftl_tr = {
0810 .name = "rfd",
0811 .major = RFD_FTL_MAJOR,
0812 .part_bits = PART_BITS,
0813 .blksize = SECTOR_SIZE,
0814
0815 .readsect = rfd_ftl_readsect,
0816 .writesect = rfd_ftl_writesect,
0817 .discard = rfd_ftl_discardsect,
0818 .getgeo = rfd_ftl_getgeo,
0819 .add_mtd = rfd_ftl_add_mtd,
0820 .remove_dev = rfd_ftl_remove_dev,
0821 .owner = THIS_MODULE,
0822 };
0823
0824 module_mtd_blktrans(rfd_ftl_tr);
0825
0826 MODULE_LICENSE("GPL");
0827 MODULE_AUTHOR("Sean Young <sean@mess.org>");
0828 MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, "
0829 "used by General Software's Embedded BIOS");
0830