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