linux-old/drivers/mtd/mtdconcat.c
<<
>>
Prefs
   1/*
   2 * MTD device concatenation layer
   3 *
   4 * (C) 2002 Robert Kaiser <rkaiser@sysgo.de>
   5 *
   6 * This code is GPL
   7 *
   8 * $Id: mtdconcat.c,v 1.3 2002/05/21 21:04:25 dwmw2 Exp $
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/types.h>
  13#include <linux/kernel.h>
  14#include <linux/slab.h>
  15
  16#include <linux/mtd/mtd.h>
  17#include <linux/mtd/concat.h>
  18
  19/*
  20 * Our storage structure:
  21 * Subdev points to an array of pointers to struct mtd_info objects
  22 * which is allocated along with this structure
  23 *
  24 */
  25struct mtd_concat {
  26        struct mtd_info mtd;
  27        int             num_subdev;
  28        struct mtd_info **subdev;
  29};
  30
  31/*
  32 * how to calculate the size required for the above structure,
  33 * including the pointer array subdev points to:
  34 */
  35#define SIZEOF_STRUCT_MTD_CONCAT(num_subdev)    \
  36        ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))
  37
  38
  39/*
  40 * Given a pointer to the MTD object in the mtd_concat structure,
  41 * we can retrieve the pointer to that structure with this macro.
  42 */
  43#define CONCAT(x)  ((struct mtd_concat *)(x))
  44
  45        
  46/* 
  47 * MTD methods which look up the relevant subdevice, translate the
  48 * effective address and pass through to the subdevice.
  49 */
  50
  51static int concat_read (struct mtd_info *mtd, loff_t from, size_t len, 
  52                        size_t *retlen, u_char *buf)
  53{
  54        struct mtd_concat *concat = CONCAT(mtd);
  55        int err = -EINVAL;
  56        int i;
  57
  58        *retlen = 0;
  59
  60        for(i = 0; i < concat->num_subdev; i++)
  61        {
  62                struct mtd_info *subdev = concat->subdev[i];
  63                size_t size, retsize;
  64
  65                if (from >= subdev->size)
  66                {
  67                        size  = 0;
  68                        from -= subdev->size;
  69                }
  70                else
  71                {
  72                        if (from + len > subdev->size)
  73                                size = subdev->size - from;
  74                        else
  75                                size = len;
  76
  77                        err = subdev->read(subdev, from, size, &retsize, buf);
  78
  79                        if(err)
  80                                break;
  81
  82                        *retlen += retsize;
  83                        len -= size;
  84                        if(len == 0)
  85                                break;
  86
  87                        err = -EINVAL;
  88                        buf += size;
  89                        from = 0;
  90                }
  91        }
  92        return err;
  93}
  94
  95static int concat_write (struct mtd_info *mtd, loff_t to, size_t len,
  96                        size_t *retlen, const u_char *buf)
  97{
  98        struct mtd_concat *concat = CONCAT(mtd);
  99        int err = -EINVAL;
 100        int i;
 101
 102        if (!(mtd->flags & MTD_WRITEABLE))
 103                return -EROFS;
 104
 105        *retlen = 0;
 106
 107        for(i = 0; i < concat->num_subdev; i++)
 108        {
 109                struct mtd_info *subdev = concat->subdev[i];
 110                size_t size, retsize;
 111
 112                if (to >= subdev->size)
 113                {
 114                        size  = 0;
 115                        to -= subdev->size;
 116                }
 117                else
 118                {
 119                        if (to + len > subdev->size)
 120                                size = subdev->size - to;
 121                        else
 122                                size = len;
 123
 124                        if (!(subdev->flags & MTD_WRITEABLE))
 125                                err = -EROFS;
 126                        else
 127                                err = subdev->write(subdev, to, size, &retsize, buf);
 128
 129                        if(err)
 130                                break;
 131
 132                        *retlen += retsize;
 133                        len -= size;
 134                        if(len == 0)
 135                                break;
 136
 137                        err = -EINVAL;
 138                        buf += size;
 139                        to = 0;
 140                }
 141        }
 142        return err;
 143}
 144
 145static void concat_erase_callback (struct erase_info *instr)
 146{
 147        wake_up((wait_queue_head_t *)instr->priv);
 148}
 149
 150static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase)
 151{
 152        int err;
 153        wait_queue_head_t waitq;
 154        DECLARE_WAITQUEUE(wait, current);
 155
 156        /*
 157         * This code was stol^H^H^H^Hinspired by mtdchar.c
 158         */
 159        init_waitqueue_head(&waitq);
 160
 161        erase->mtd = mtd;
 162        erase->callback = concat_erase_callback;
 163        erase->priv = (unsigned long)&waitq;
 164                        
 165        /*
 166         * FIXME: Allow INTERRUPTIBLE. Which means
 167         * not having the wait_queue head on the stack.
 168         */
 169        err = mtd->erase(mtd, erase);
 170        if (!err)
 171        {
 172                set_current_state(TASK_UNINTERRUPTIBLE);
 173                add_wait_queue(&waitq, &wait);
 174                if (erase->state != MTD_ERASE_DONE && erase->state != MTD_ERASE_FAILED)
 175                        schedule();
 176                remove_wait_queue(&waitq, &wait);
 177                set_current_state(TASK_RUNNING);
 178
 179                err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0;
 180        }
 181        return err;
 182}
 183
 184static int concat_erase (struct mtd_info *mtd, struct erase_info *instr)
 185{
 186        struct mtd_concat *concat = CONCAT(mtd);
 187        struct mtd_info *subdev;
 188        int i, err;
 189        u_int32_t length;
 190        struct erase_info *erase;
 191
 192        if (!(mtd->flags & MTD_WRITEABLE))
 193                return -EROFS;
 194
 195        if(instr->addr > concat->mtd.size)
 196                return -EINVAL;
 197
 198        if(instr->len + instr->addr > concat->mtd.size)
 199                return -EINVAL;
 200
 201        /*
 202         * Check for proper erase block alignment of the to-be-erased area.
 203         * It is easier to do this based on the super device's erase
 204         * region info rather than looking at each particular sub-device
 205         * in turn.
 206         */
 207        if (!concat->mtd.numeraseregions)
 208        {       /* the easy case: device has uniform erase block size */
 209                if(instr->addr & (concat->mtd.erasesize - 1))
 210                        return -EINVAL;
 211                if(instr->len & (concat->mtd.erasesize - 1))
 212                        return -EINVAL;
 213        }
 214        else
 215        {       /* device has variable erase size */
 216                struct mtd_erase_region_info *erase_regions = concat->mtd.eraseregions;
 217
 218                /*
 219                 * Find the erase region where the to-be-erased area begins:
 220                 */
 221                for(i = 0; i < concat->mtd.numeraseregions && 
 222                           instr->addr >= erase_regions[i].offset; i++)
 223                        ;
 224                --i;
 225
 226                /*
 227                 * Now erase_regions[i] is the region in which the
 228                 * to-be-erased area begins. Verify that the starting
 229                 * offset is aligned to this region's erase size:
 230                 */
 231                if (instr->addr & (erase_regions[i].erasesize-1))
 232                        return -EINVAL;
 233
 234                /*
 235                 * now find the erase region where the to-be-erased area ends:
 236                 */
 237                for(; i < concat->mtd.numeraseregions && 
 238                      (instr->addr + instr->len) >=  erase_regions[i].offset ; ++i)
 239                        ;
 240                --i;
 241                /*
 242                 * check if the ending offset is aligned to this region's erase size
 243                 */
 244                if ((instr->addr + instr->len) & (erase_regions[i].erasesize-1))
 245                        return -EINVAL;
 246        }
 247
 248        /* make a local copy of instr to avoid modifying the caller's struct */
 249        erase = kmalloc(sizeof(struct erase_info),GFP_KERNEL);
 250
 251        if (!erase)
 252                return -ENOMEM;
 253
 254        *erase = *instr;
 255        length = instr->len;
 256
 257        /*
 258         * find the subdevice where the to-be-erased area begins, adjust
 259         * starting offset to be relative to the subdevice start
 260         */
 261        for(i = 0; i < concat->num_subdev; i++)
 262        {
 263                subdev = concat->subdev[i];
 264                if(subdev->size <= erase->addr)
 265                        erase->addr -= subdev->size;
 266                else
 267                        break;
 268    }
 269        if(i >= concat->num_subdev)     /* must never happen since size */
 270                BUG();                                  /* limit has been verified above */
 271
 272        /* now do the erase: */
 273        err = 0;
 274        for(;length > 0; i++)   /* loop for all subevices affected by this request */
 275        {
 276                subdev = concat->subdev[i];             /* get current subdevice */
 277
 278                /* limit length to subdevice's size: */
 279                if(erase->addr + length > subdev->size)
 280                        erase->len = subdev->size - erase->addr;
 281                else
 282                        erase->len = length;
 283
 284                if (!(subdev->flags & MTD_WRITEABLE))
 285                {
 286                        err = -EROFS;
 287                        break;
 288                }
 289                length -= erase->len;
 290                if ((err = concat_dev_erase(subdev, erase)))
 291                {
 292                        if(err == -EINVAL)      /* sanity check: must never happen since */
 293                                BUG();                  /* block alignment has been checked above */
 294                        break;
 295                }
 296                /*
 297                 * erase->addr specifies the offset of the area to be
 298                 * erased *within the current subdevice*. It can be
 299                 * non-zero only the first time through this loop, i.e.
 300                 * for the first subdevice where blocks need to be erased.
 301                 * All the following erases must begin at the start of the
 302                 * current subdevice, i.e. at offset zero.
 303                 */
 304                erase->addr = 0;
 305        }
 306        kfree(erase);
 307        if (err)
 308                return err;
 309
 310        instr->state = MTD_ERASE_DONE;
 311        if (instr->callback)
 312                instr->callback(instr);
 313        return 0;
 314}
 315
 316static int concat_lock (struct mtd_info *mtd, loff_t ofs, size_t len)
 317{
 318        struct mtd_concat *concat = CONCAT(mtd);
 319        int i, err = -EINVAL;
 320
 321        if ((len + ofs) > mtd->size) 
 322                return -EINVAL;
 323
 324        for(i = 0; i < concat->num_subdev; i++)
 325        {
 326                struct mtd_info *subdev = concat->subdev[i];
 327                size_t size;
 328
 329                if (ofs >= subdev->size)
 330                {
 331                        size  = 0;
 332                        ofs -= subdev->size;
 333                }
 334                else
 335                {
 336                        if (ofs + len > subdev->size)
 337                                size = subdev->size - ofs;
 338                        else
 339                                size = len;
 340
 341                        err = subdev->lock(subdev, ofs, size);
 342
 343                        if(err)
 344                                break;
 345
 346                        len -= size;
 347                        if(len == 0)
 348                                break;
 349
 350                        err = -EINVAL;
 351                        ofs = 0;
 352                }
 353        }
 354        return err;
 355}
 356
 357static int concat_unlock (struct mtd_info *mtd, loff_t ofs, size_t len)
 358{
 359        struct mtd_concat *concat = CONCAT(mtd);
 360        int i, err = 0;
 361
 362        if ((len + ofs) > mtd->size) 
 363                return -EINVAL;
 364
 365        for(i = 0; i < concat->num_subdev; i++)
 366        {
 367                struct mtd_info *subdev = concat->subdev[i];
 368                size_t size;
 369
 370                if (ofs >= subdev->size)
 371                {
 372                        size  = 0;
 373                        ofs -= subdev->size;
 374                }
 375                else
 376                {
 377                        if (ofs + len > subdev->size)
 378                                size = subdev->size - ofs;
 379                        else
 380                                size = len;
 381
 382                        err = subdev->unlock(subdev, ofs, size);
 383
 384                        if(err)
 385                                break;
 386
 387                        len -= size;
 388                        if(len == 0)
 389                                break;
 390
 391                        err = -EINVAL;
 392                        ofs = 0;
 393                }
 394        }
 395        return err;
 396}
 397
 398static void concat_sync(struct mtd_info *mtd)
 399{
 400        struct mtd_concat *concat = CONCAT(mtd);
 401        int i;
 402
 403        for(i = 0; i < concat->num_subdev; i++)
 404        {
 405                struct mtd_info *subdev = concat->subdev[i];
 406                subdev->sync(subdev);
 407        }
 408}
 409
 410static int concat_suspend(struct mtd_info *mtd)
 411{
 412        struct mtd_concat *concat = CONCAT(mtd);
 413        int i, rc = 0;
 414
 415        for(i = 0; i < concat->num_subdev; i++)
 416        {
 417                struct mtd_info *subdev = concat->subdev[i];
 418                if((rc = subdev->suspend(subdev)) < 0)
 419                        return rc;
 420        }
 421        return rc;
 422}
 423
 424static void concat_resume(struct mtd_info *mtd)
 425{
 426        struct mtd_concat *concat = CONCAT(mtd);
 427        int i;
 428
 429        for(i = 0; i < concat->num_subdev; i++)
 430        {
 431                struct mtd_info *subdev = concat->subdev[i];
 432                subdev->resume(subdev);
 433        }
 434}
 435
 436/*
 437 * This function constructs a virtual MTD device by concatenating
 438 * num_devs MTD devices. A pointer to the new device object is
 439 * stored to *new_dev upon success. This function does _not_
 440 * register any devices: this is the caller's responsibility.
 441 */
 442struct mtd_info *mtd_concat_create(
 443        struct mtd_info *subdev[],      /* subdevices to concatenate */
 444        int num_devs,                           /* number of subdevices      */
 445        char *name)                                     /* name for the new device   */
 446{
 447        int i;
 448        size_t size;
 449        struct mtd_concat *concat;
 450        u_int32_t max_erasesize, curr_erasesize;
 451        int num_erase_region;
 452
 453        printk(KERN_NOTICE "Concatenating MTD devices:\n");
 454        for(i = 0; i < num_devs; i++)
 455                printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name);
 456        printk(KERN_NOTICE "into device \"%s\"\n", name);
 457
 458        /* allocate the device structure */
 459        size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
 460        concat = kmalloc (size, GFP_KERNEL);
 461        if(!concat)
 462        {
 463                printk ("memory allocation error while creating concatenated device \"%s\"\n",
 464                                name);
 465                        return NULL;
 466        }
 467        memset(concat, 0, size);
 468        concat->subdev = (struct mtd_info **)(concat + 1);
 469
 470        /*
 471         * Set up the new "super" device's MTD object structure, check for
 472         * incompatibilites between the subdevices.
 473         */
 474        concat->mtd.type      = subdev[0]->type;
 475        concat->mtd.flags     = subdev[0]->flags;
 476        concat->mtd.size      = subdev[0]->size;
 477        concat->mtd.erasesize = subdev[0]->erasesize;
 478        concat->mtd.oobblock  = subdev[0]->oobblock;
 479        concat->mtd.oobsize   = subdev[0]->oobsize;
 480        concat->mtd.ecctype   = subdev[0]->ecctype;
 481        concat->mtd.eccsize   = subdev[0]->eccsize;
 482
 483        concat->subdev[0]   = subdev[0];
 484
 485        for(i = 1; i < num_devs; i++)
 486        {
 487                if(concat->mtd.type != subdev[i]->type)
 488                {
 489                        kfree(concat);
 490                        printk ("Incompatible device type on \"%s\"\n", subdev[i]->name);
 491                        return NULL;
 492                }
 493                if(concat->mtd.flags != subdev[i]->flags)
 494                {       /*
 495                         * Expect all flags except MTD_WRITEABLE to be equal on
 496                         * all subdevices.
 497                         */
 498                        if((concat->mtd.flags ^ subdev[i]->flags) & ~MTD_WRITEABLE)
 499                        {
 500                                kfree(concat);
 501                                printk ("Incompatible device flags on \"%s\"\n", subdev[i]->name);
 502                                return NULL;
 503                        }
 504                        else    /* if writeable attribute differs, make super device writeable */
 505                                concat->mtd.flags |= subdev[i]->flags & MTD_WRITEABLE;
 506                }
 507                concat->mtd.size += subdev[i]->size;
 508                if(concat->mtd.oobblock != subdev[i]->oobblock ||
 509                   concat->mtd.oobsize  != subdev[i]->oobsize  ||
 510                   concat->mtd.ecctype  != subdev[i]->ecctype  ||
 511                   concat->mtd.eccsize  != subdev[i]->eccsize)
 512                {
 513                        kfree(concat);
 514                        printk ("Incompatible OOB or ECC data on \"%s\"\n", subdev[i]->name);
 515                        return NULL;
 516                }
 517                concat->subdev[i] = subdev[i];
 518                
 519        }
 520
 521        concat->num_subdev  = num_devs;
 522        concat->mtd.name    = name;
 523
 524        /*
 525         * NOTE: for now, we do not provide any readv()/writev() methods
 526         *       because they are messy to implement and they are not
 527         *       used to a great extent anyway.
 528         */
 529        concat->mtd.erase   = concat_erase;
 530        concat->mtd.read    = concat_read;
 531        concat->mtd.write   = concat_write;
 532        concat->mtd.sync    = concat_sync;
 533        concat->mtd.lock    = concat_lock;
 534        concat->mtd.unlock  = concat_unlock;
 535        concat->mtd.suspend = concat_suspend;
 536        concat->mtd.resume  = concat_resume;
 537
 538
 539        /*
 540         * Combine the erase block size info of the subdevices:
 541         *
 542         * first, walk the map of the new device and see how
 543         * many changes in erase size we have
 544         */
 545        max_erasesize = curr_erasesize = subdev[0]->erasesize;
 546        num_erase_region = 1;
 547        for(i = 0; i < num_devs; i++)
 548        {
 549                if(subdev[i]->numeraseregions == 0)
 550                {       /* current subdevice has uniform erase size */
 551                        if(subdev[i]->erasesize != curr_erasesize)
 552                        {       /* if it differs from the last subdevice's erase size, count it */
 553                                ++num_erase_region;
 554                                curr_erasesize = subdev[i]->erasesize;
 555                                if(curr_erasesize > max_erasesize)
 556                                        max_erasesize = curr_erasesize;
 557                        }
 558                }
 559                else
 560                {       /* current subdevice has variable erase size */
 561                        int j;
 562                        for(j = 0; j < subdev[i]->numeraseregions; j++)
 563                        {       /* walk the list of erase regions, count any changes */
 564                                if(subdev[i]->eraseregions[j].erasesize != curr_erasesize)
 565                                {
 566                                        ++num_erase_region;
 567                                        curr_erasesize = subdev[i]->eraseregions[j].erasesize;
 568                                        if(curr_erasesize > max_erasesize)
 569                                                max_erasesize = curr_erasesize;
 570                                }
 571                        }
 572                }
 573        }
 574
 575        if(num_erase_region == 1)
 576        {       /*
 577                 * All subdevices have the same uniform erase size.
 578                 * This is easy:
 579                 */
 580                concat->mtd.erasesize = curr_erasesize;
 581                concat->mtd.numeraseregions = 0;
 582        }
 583        else
 584        {       /*
 585                 * erase block size varies across the subdevices: allocate
 586                 * space to store the data describing the variable erase regions
 587                 */
 588                struct mtd_erase_region_info *erase_region_p;
 589                u_int32_t begin, position;
 590
 591                concat->mtd.erasesize = max_erasesize;
 592                concat->mtd.numeraseregions = num_erase_region;
 593                concat->mtd.eraseregions = erase_region_p = kmalloc (
 594                     num_erase_region * sizeof(struct mtd_erase_region_info), GFP_KERNEL);
 595                if(!erase_region_p)
 596                {
 597                        kfree(concat);
 598                        printk ("memory allocation error while creating erase region list"
 599                                " for device \"%s\"\n", name);
 600                        return NULL;
 601                }
 602
 603                /*
 604                 * walk the map of the new device once more and fill in
 605                 * in erase region info:
 606                 */
 607                curr_erasesize = subdev[0]->erasesize;
 608                begin = position = 0;
 609                for(i = 0; i < num_devs; i++)
 610                {
 611                        if(subdev[i]->numeraseregions == 0)
 612                        {       /* current subdevice has uniform erase size */
 613                                if(subdev[i]->erasesize != curr_erasesize)
 614                                {       /*
 615                                         *  fill in an mtd_erase_region_info structure for the area
 616                                         *  we have walked so far:
 617                                         */
 618                                        erase_region_p->offset    = begin;
 619                                        erase_region_p->erasesize = curr_erasesize;
 620                                        erase_region_p->numblocks = (position - begin) / curr_erasesize;
 621                                        begin = position;
 622
 623                                        curr_erasesize = subdev[i]->erasesize;
 624                                        ++erase_region_p;
 625                                }
 626                                position += subdev[i]->size;
 627                        }
 628                        else
 629                        {       /* current subdevice has variable erase size */
 630                                int j;
 631                                for(j = 0; j < subdev[i]->numeraseregions; j++)
 632                                {       /* walk the list of erase regions, count any changes */
 633                                        if(subdev[i]->eraseregions[j].erasesize != curr_erasesize)
 634                                        {
 635                                                erase_region_p->offset    = begin;
 636                                                erase_region_p->erasesize = curr_erasesize;
 637                                                erase_region_p->numblocks = (position - begin) / curr_erasesize;
 638                                                begin = position;
 639
 640                                                curr_erasesize = subdev[i]->eraseregions[j].erasesize;
 641                                                ++erase_region_p;
 642                                        }
 643                                        position += subdev[i]->eraseregions[j].numblocks * curr_erasesize;
 644                                }
 645                        }
 646                }
 647                /* Now write the final entry */
 648                erase_region_p->offset    = begin;
 649                erase_region_p->erasesize = curr_erasesize;
 650                erase_region_p->numblocks = (position - begin) / curr_erasesize;
 651        }
 652
 653        return &concat->mtd;
 654}
 655
 656/* 
 657 * This function destroys an MTD object obtained from concat_mtd_devs()
 658 */
 659
 660void mtd_concat_destroy(struct mtd_info *mtd)
 661{
 662        struct mtd_concat *concat = CONCAT(mtd);
 663        if(concat->mtd.numeraseregions)
 664                kfree(concat->mtd.eraseregions);
 665        kfree(concat);
 666}
 667
 668
 669EXPORT_SYMBOL(mtd_concat_create);
 670EXPORT_SYMBOL(mtd_concat_destroy);
 671
 672
 673MODULE_LICENSE("GPL");
 674MODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>");
 675MODULE_DESCRIPTION("Generic support for concatenating of MTD devices");
 676