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