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