linux-old/drivers/mtd/mtdblock.c
<<
>>
Prefs
   1/* 
   2 * Direct MTD block device access
   3 *
   4 * $Id: mtdblock.c,v 1.47 2001/10/02 15:05:11 dwmw2 Exp $
   5 *
   6 * 02-nov-2000  Nicolas Pitre           Added read-modify-write with cache
   7 */
   8
   9#include <linux/config.h>
  10#include <linux/types.h>
  11#include <linux/module.h>
  12#include <linux/kernel.h>
  13#include <linux/slab.h>
  14#include <linux/mtd/mtd.h>
  15#include <linux/mtd/compatmac.h>
  16
  17#define MAJOR_NR MTD_BLOCK_MAJOR
  18#define DEVICE_NAME "mtdblock"
  19#define DEVICE_REQUEST mtdblock_request
  20#define DEVICE_NR(device) (device)
  21#define DEVICE_ON(device)
  22#define DEVICE_OFF(device)
  23#define DEVICE_NO_RANDOM
  24#include <linux/blk.h>
  25/* for old kernels... */
  26#ifndef QUEUE_EMPTY
  27#define QUEUE_EMPTY  (!CURRENT)
  28#endif
  29#if LINUX_VERSION_CODE < 0x20300
  30#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].plug_tq.sync)
  31#else
  32#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].request_queue.plugged)
  33#endif
  34
  35#ifdef CONFIG_DEVFS_FS
  36#include <linux/devfs_fs_kernel.h>
  37static void mtd_notify_add(struct mtd_info* mtd);
  38static void mtd_notify_remove(struct mtd_info* mtd);
  39static struct mtd_notifier notifier = {
  40        mtd_notify_add,
  41        mtd_notify_remove,
  42        NULL
  43};
  44static devfs_handle_t devfs_dir_handle = NULL;
  45static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES];
  46#endif
  47
  48static struct mtdblk_dev {
  49        struct mtd_info *mtd; /* Locked */
  50        int count;
  51        struct semaphore cache_sem;
  52        unsigned char *cache_data;
  53        unsigned long cache_offset;
  54        unsigned int cache_size;
  55        enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
  56} *mtdblks[MAX_MTD_DEVICES];
  57
  58static spinlock_t mtdblks_lock;
  59
  60static int mtd_sizes[MAX_MTD_DEVICES];
  61static int mtd_blksizes[MAX_MTD_DEVICES];
  62
  63
  64/*
  65 * Cache stuff...
  66 * 
  67 * Since typical flash erasable sectors are much larger than what Linux's
  68 * buffer cache can handle, we must implement read-modify-write on flash
  69 * sectors for each block write requests.  To avoid over-erasing flash sectors
  70 * and to speed things up, we locally cache a whole flash sector while it is
  71 * being written to until a different sector is required.
  72 */
  73
  74static void erase_callback(struct erase_info *done)
  75{
  76        wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
  77        wake_up(wait_q);
  78}
  79
  80static int erase_write (struct mtd_info *mtd, unsigned long pos, 
  81                        int len, const char *buf)
  82{
  83        struct erase_info erase;
  84        DECLARE_WAITQUEUE(wait, current);
  85        wait_queue_head_t wait_q;
  86        size_t retlen;
  87        int ret;
  88
  89        /*
  90         * First, let's erase the flash block.
  91         */
  92
  93        init_waitqueue_head(&wait_q);
  94        erase.mtd = mtd;
  95        erase.callback = erase_callback;
  96        erase.addr = pos;
  97        erase.len = len;
  98        erase.priv = (u_long)&wait_q;
  99
 100        set_current_state(TASK_INTERRUPTIBLE);
 101        add_wait_queue(&wait_q, &wait);
 102
 103        ret = MTD_ERASE(mtd, &erase);
 104        if (ret) {
 105                set_current_state(TASK_RUNNING);
 106                remove_wait_queue(&wait_q, &wait);
 107                printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] "
 108                                     "on \"%s\" failed\n",
 109                        pos, len, mtd->name);
 110                return ret;
 111        }
 112
 113        schedule();  /* Wait for erase to finish. */
 114        remove_wait_queue(&wait_q, &wait);
 115
 116        /*
 117         * Next, writhe data to flash.
 118         */
 119
 120        ret = MTD_WRITE (mtd, pos, len, &retlen, buf);
 121        if (ret)
 122                return ret;
 123        if (retlen != len)
 124                return -EIO;
 125        return 0;
 126}
 127
 128
 129static int write_cached_data (struct mtdblk_dev *mtdblk)
 130{
 131        struct mtd_info *mtd = mtdblk->mtd;
 132        int ret;
 133
 134        if (mtdblk->cache_state != STATE_DIRTY)
 135                return 0;
 136
 137        DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" "
 138                        "at 0x%lx, size 0x%x\n", mtd->name, 
 139                        mtdblk->cache_offset, mtdblk->cache_size);
 140        
 141        ret = erase_write (mtd, mtdblk->cache_offset, 
 142                           mtdblk->cache_size, mtdblk->cache_data);
 143        if (ret)
 144                return ret;
 145
 146        /*
 147         * Here we could argably set the cache state to STATE_CLEAN.
 148         * However this could lead to inconsistency since we will not 
 149         * be notified if this content is altered on the flash by other 
 150         * means.  Let's declare it empty and leave buffering tasks to
 151         * the buffer cache instead.
 152         */
 153        mtdblk->cache_state = STATE_EMPTY;
 154        return 0;
 155}
 156
 157
 158static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, 
 159                            int len, const char *buf)
 160{
 161        struct mtd_info *mtd = mtdblk->mtd;
 162        unsigned int sect_size = mtdblk->cache_size;
 163        size_t retlen;
 164        int ret;
 165
 166        DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n",
 167                mtd->name, pos, len);
 168        
 169        if (!sect_size)
 170                return MTD_WRITE (mtd, pos, len, &retlen, buf);
 171
 172        while (len > 0) {
 173                unsigned long sect_start = (pos/sect_size)*sect_size;
 174                unsigned int offset = pos - sect_start;
 175                unsigned int size = sect_size - offset;
 176                if( size > len ) 
 177                        size = len;
 178
 179                if (size == sect_size) {
 180                        /* 
 181                         * We are covering a whole sector.  Thus there is no
 182                         * need to bother with the cache while it may still be
 183                         * useful for other partial writes.
 184                         */
 185                        ret = erase_write (mtd, pos, size, buf);
 186                        if (ret)
 187                                return ret;
 188                } else {
 189                        /* Partial sector: need to use the cache */
 190
 191                        if (mtdblk->cache_state == STATE_DIRTY &&
 192                            mtdblk->cache_offset != sect_start) {
 193                                ret = write_cached_data(mtdblk);
 194                                if (ret) 
 195                                        return ret;
 196                        }
 197
 198                        if (mtdblk->cache_state == STATE_EMPTY ||
 199                            mtdblk->cache_offset != sect_start) {
 200                                /* fill the cache with the current sector */
 201                                mtdblk->cache_state = STATE_EMPTY;
 202                                ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data);
 203                                if (ret)
 204                                        return ret;
 205                                if (retlen != sect_size)
 206                                        return -EIO;
 207
 208                                mtdblk->cache_offset = sect_start;
 209                                mtdblk->cache_size = sect_size;
 210                                mtdblk->cache_state = STATE_CLEAN;
 211                        }
 212
 213                        /* write data to our local cache */
 214                        memcpy (mtdblk->cache_data + offset, buf, size);
 215                        mtdblk->cache_state = STATE_DIRTY;
 216                }
 217
 218                buf += size;
 219                pos += size;
 220                len -= size;
 221        }
 222
 223        return 0;
 224}
 225
 226
 227static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, 
 228                           int len, char *buf)
 229{
 230        struct mtd_info *mtd = mtdblk->mtd;
 231        unsigned int sect_size = mtdblk->cache_size;
 232        size_t retlen;
 233        int ret;
 234
 235        DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n", 
 236                        mtd->name, pos, len);
 237        
 238        if (!sect_size)
 239                return MTD_READ (mtd, pos, len, &retlen, buf);
 240
 241        while (len > 0) {
 242                unsigned long sect_start = (pos/sect_size)*sect_size;
 243                unsigned int offset = pos - sect_start;
 244                unsigned int size = sect_size - offset;
 245                if (size > len) 
 246                        size = len;
 247
 248                /*
 249                 * Check if the requested data is already cached
 250                 * Read the requested amount of data from our internal cache if it
 251                 * contains what we want, otherwise we read the data directly
 252                 * from flash.
 253                 */
 254                if (mtdblk->cache_state != STATE_EMPTY &&
 255                    mtdblk->cache_offset == sect_start) {
 256                        memcpy (buf, mtdblk->cache_data + offset, size);
 257                } else {
 258                        ret = MTD_READ (mtd, pos, size, &retlen, buf);
 259                        if (ret)
 260                                return ret;
 261                        if (retlen != size)
 262                                return -EIO;
 263                }
 264
 265                buf += size;
 266                pos += size;
 267                len -= size;
 268        }
 269
 270        return 0;
 271}
 272
 273
 274
 275static int mtdblock_open(struct inode *inode, struct file *file)
 276{
 277        struct mtdblk_dev *mtdblk;
 278        struct mtd_info *mtd;
 279        int dev;
 280
 281        DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n");
 282        
 283        if (!inode)
 284                return -EINVAL;
 285        
 286        dev = MINOR(inode->i_rdev);
 287        if (dev >= MAX_MTD_DEVICES)
 288                return -EINVAL;
 289
 290        mtd = get_mtd_device(NULL, dev);
 291        if (!mtd)
 292                return -ENODEV;
 293        if (MTD_ABSENT == mtd->type) {
 294                put_mtd_device(mtd);
 295                return -ENODEV;
 296        }
 297        
 298        spin_lock(&mtdblks_lock);
 299
 300        /* If it's already open, no need to piss about. */
 301        if (mtdblks[dev]) {
 302                mtdblks[dev]->count++;
 303                spin_unlock(&mtdblks_lock);
 304                return 0;
 305        }
 306        
 307        /* OK, it's not open. Try to find it */
 308
 309        /* First we have to drop the lock, because we have to
 310           to things which might sleep.
 311        */
 312        spin_unlock(&mtdblks_lock);
 313
 314        mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);
 315        if (!mtdblk) {
 316                put_mtd_device(mtd);
 317                return -ENOMEM;
 318        }
 319        memset(mtdblk, 0, sizeof(*mtdblk));
 320        mtdblk->count = 1;
 321        mtdblk->mtd = mtd;
 322
 323        init_MUTEX (&mtdblk->cache_sem);
 324        mtdblk->cache_state = STATE_EMPTY;
 325        if ((mtdblk->mtd->flags & MTD_CAP_RAM) != MTD_CAP_RAM &&
 326            mtdblk->mtd->erasesize) {
 327                mtdblk->cache_size = mtdblk->mtd->erasesize;
 328                mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize);
 329                if (!mtdblk->cache_data) {
 330                        put_mtd_device(mtdblk->mtd);
 331                        kfree(mtdblk);
 332                        return -ENOMEM;
 333                }
 334        }
 335
 336        /* OK, we've created a new one. Add it to the list. */
 337
 338        spin_lock(&mtdblks_lock);
 339
 340        if (mtdblks[dev]) {
 341                /* Another CPU made one at the same time as us. */
 342                mtdblks[dev]->count++;
 343                spin_unlock(&mtdblks_lock);
 344                put_mtd_device(mtdblk->mtd);
 345                vfree(mtdblk->cache_data);
 346                kfree(mtdblk);
 347                return 0;
 348        }
 349
 350        mtdblks[dev] = mtdblk;
 351        mtd_sizes[dev] = mtdblk->mtd->size/1024;
 352        if (mtdblk->mtd->erasesize)
 353                mtd_blksizes[dev] = mtdblk->mtd->erasesize;
 354        if (mtd_blksizes[dev] > PAGE_SIZE)
 355                mtd_blksizes[dev] = PAGE_SIZE;
 356        set_device_ro (inode->i_rdev, !(mtdblk->mtd->flags & MTD_WRITEABLE));
 357        
 358        spin_unlock(&mtdblks_lock);
 359        
 360        DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
 361
 362        return 0;
 363}
 364
 365static release_t mtdblock_release(struct inode *inode, struct file *file)
 366{
 367        int dev;
 368        struct mtdblk_dev *mtdblk;
 369        DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n");
 370
 371        if (inode == NULL)
 372                release_return(-ENODEV);
 373
 374        invalidate_device(inode->i_rdev, 1);
 375
 376        dev = MINOR(inode->i_rdev);
 377        mtdblk = mtdblks[dev];
 378
 379        down(&mtdblk->cache_sem);
 380        write_cached_data(mtdblk);
 381        up(&mtdblk->cache_sem);
 382
 383        spin_lock(&mtdblks_lock);
 384        if (!--mtdblk->count) {
 385                /* It was the last usage. Free the device */
 386                mtdblks[dev] = NULL;
 387                spin_unlock(&mtdblks_lock);
 388                if (mtdblk->mtd->sync)
 389                        mtdblk->mtd->sync(mtdblk->mtd);
 390                put_mtd_device(mtdblk->mtd);
 391                vfree(mtdblk->cache_data);
 392                kfree(mtdblk);
 393        } else {
 394                spin_unlock(&mtdblks_lock);
 395        }
 396
 397        DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
 398
 399        release_return(0);
 400}  
 401
 402
 403/* 
 404 * This is a special request_fn because it is executed in a process context 
 405 * to be able to sleep independently of the caller.  The io_request_lock 
 406 * is held upon entry and exit.
 407 * The head of our request queue is considered active so there is no need 
 408 * to dequeue requests before we are done.
 409 */
 410static void handle_mtdblock_request(void)
 411{
 412        struct request *req;
 413        struct mtdblk_dev *mtdblk;
 414        unsigned int res;
 415
 416        for (;;) {
 417                INIT_REQUEST;
 418                req = CURRENT;
 419                spin_unlock_irq(&io_request_lock);
 420                mtdblk = mtdblks[MINOR(req->rq_dev)];
 421                res = 0;
 422
 423                if (MINOR(req->rq_dev) >= MAX_MTD_DEVICES)
 424                        panic(__FUNCTION__": minor out of bound");
 425
 426                if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9))
 427                        goto end_req;
 428
 429                // Handle the request
 430                switch (req->cmd)
 431                {
 432                        int err;
 433
 434                        case READ:
 435                        down(&mtdblk->cache_sem);
 436                        err = do_cached_read (mtdblk, req->sector << 9, 
 437                                        req->current_nr_sectors << 9,
 438                                        req->buffer);
 439                        up(&mtdblk->cache_sem);
 440                        if (!err)
 441                                res = 1;
 442                        break;
 443
 444                        case WRITE:
 445                        // Read only device
 446                        if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) ) 
 447                                break;
 448
 449                        // Do the write
 450                        down(&mtdblk->cache_sem);
 451                        err = do_cached_write (mtdblk, req->sector << 9,
 452                                        req->current_nr_sectors << 9, 
 453                                        req->buffer);
 454                        up(&mtdblk->cache_sem);
 455                        if (!err)
 456                                res = 1;
 457                        break;
 458                }
 459
 460end_req:
 461                spin_lock_irq(&io_request_lock);
 462                end_request(res);
 463        }
 464}
 465
 466static volatile int leaving = 0;
 467static DECLARE_MUTEX_LOCKED(thread_sem);
 468static DECLARE_WAIT_QUEUE_HEAD(thr_wq);
 469
 470int mtdblock_thread(void *dummy)
 471{
 472        struct task_struct *tsk = current;
 473        DECLARE_WAITQUEUE(wait, tsk);
 474
 475        tsk->session = 1;
 476        tsk->pgrp = 1;
 477        /* we might get involved when memory gets low, so use PF_MEMALLOC */
 478        tsk->flags |= PF_MEMALLOC;
 479        strcpy(tsk->comm, "mtdblockd");
 480        tsk->tty = NULL;
 481        spin_lock_irq(&tsk->sigmask_lock);
 482        sigfillset(&tsk->blocked);
 483        recalc_sigpending(tsk);
 484        spin_unlock_irq(&tsk->sigmask_lock);
 485        exit_mm(tsk);
 486        exit_files(tsk);
 487        exit_sighand(tsk);
 488        exit_fs(tsk);
 489
 490        while (!leaving) {
 491                add_wait_queue(&thr_wq, &wait);
 492                set_current_state(TASK_INTERRUPTIBLE);
 493                spin_lock_irq(&io_request_lock);
 494                if (QUEUE_EMPTY || QUEUE_PLUGGED) {
 495                        spin_unlock_irq(&io_request_lock);
 496                        schedule();
 497                        remove_wait_queue(&thr_wq, &wait); 
 498                } else {
 499                        remove_wait_queue(&thr_wq, &wait); 
 500                        set_current_state(TASK_RUNNING);
 501                        handle_mtdblock_request();
 502                        spin_unlock_irq(&io_request_lock);
 503                }
 504        }
 505
 506        up(&thread_sem);
 507        return 0;
 508}
 509
 510#if LINUX_VERSION_CODE < 0x20300
 511#define RQFUNC_ARG void
 512#else
 513#define RQFUNC_ARG request_queue_t *q
 514#endif
 515
 516static void mtdblock_request(RQFUNC_ARG)
 517{
 518        /* Don't do anything, except wake the thread if necessary */
 519        wake_up(&thr_wq);
 520}
 521
 522
 523static int mtdblock_ioctl(struct inode * inode, struct file * file,
 524                      unsigned int cmd, unsigned long arg)
 525{
 526        struct mtdblk_dev *mtdblk;
 527
 528        mtdblk = mtdblks[MINOR(inode->i_rdev)];
 529
 530#ifdef PARANOIA
 531        if (!mtdblk)
 532                BUG();
 533#endif
 534
 535        switch (cmd) {
 536        case BLKGETSIZE:   /* Return device size */
 537                return put_user((mtdblk->mtd->size >> 9), (unsigned long *) arg);
 538        case BLKGETSIZE64:
 539                return put_user((u64)mtdblk->mtd->size, (u64 *)arg);
 540                
 541        case BLKFLSBUF:
 542#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
 543                if(!capable(CAP_SYS_ADMIN))
 544                        return -EACCES;
 545#endif
 546                fsync_dev(inode->i_rdev);
 547                invalidate_buffers(inode->i_rdev);
 548                down(&mtdblk->cache_sem);
 549                write_cached_data(mtdblk);
 550                up(&mtdblk->cache_sem);
 551                if (mtdblk->mtd->sync)
 552                        mtdblk->mtd->sync(mtdblk->mtd);
 553                return 0;
 554
 555        default:
 556                return -EINVAL;
 557        }
 558}
 559
 560#if LINUX_VERSION_CODE < 0x20326
 561static struct file_operations mtd_fops =
 562{
 563        open: mtdblock_open,
 564        ioctl: mtdblock_ioctl,
 565        release: mtdblock_release,
 566        read: block_read,
 567        write: block_write
 568};
 569#else
 570static struct block_device_operations mtd_fops = 
 571{
 572        owner: THIS_MODULE,
 573        open: mtdblock_open,
 574        release: mtdblock_release,
 575        ioctl: mtdblock_ioctl
 576};
 577#endif
 578
 579#ifdef CONFIG_DEVFS_FS
 580/* Notification that a new device has been added. Create the devfs entry for
 581 * it. */
 582
 583static void mtd_notify_add(struct mtd_info* mtd)
 584{
 585        char name[8];
 586
 587        if (!mtd || mtd->type == MTD_ABSENT)
 588                return;
 589
 590        sprintf(name, "%d", mtd->index);
 591        devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
 592                        DEVFS_FL_DEFAULT, MTD_BLOCK_MAJOR, mtd->index,
 593                        S_IFBLK | S_IRUGO | S_IWUGO,
 594                        &mtd_fops, NULL);
 595}
 596
 597static void mtd_notify_remove(struct mtd_info* mtd)
 598{
 599        if (!mtd || mtd->type == MTD_ABSENT)
 600                return;
 601
 602        devfs_unregister(devfs_rw_handle[mtd->index]);
 603}
 604#endif
 605
 606int __init init_mtdblock(void)
 607{
 608        int i;
 609
 610        spin_lock_init(&mtdblks_lock);
 611#ifdef CONFIG_DEVFS_FS
 612        if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops))
 613        {
 614                printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
 615                        MTD_BLOCK_MAJOR);
 616                return -EAGAIN;
 617        }
 618
 619        devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL);
 620        register_mtd_user(&notifier);
 621#else
 622        if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) {
 623                printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
 624                       MTD_BLOCK_MAJOR);
 625                return -EAGAIN;
 626        }
 627#endif
 628        
 629        /* We fill it in at open() time. */
 630        for (i=0; i< MAX_MTD_DEVICES; i++) {
 631                mtd_sizes[i] = 0;
 632                mtd_blksizes[i] = BLOCK_SIZE;
 633        }
 634        init_waitqueue_head(&thr_wq);
 635        /* Allow the block size to default to BLOCK_SIZE. */
 636        blksize_size[MAJOR_NR] = mtd_blksizes;
 637        blk_size[MAJOR_NR] = mtd_sizes;
 638        
 639        blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request);
 640        kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
 641        return 0;
 642}
 643
 644static void __exit cleanup_mtdblock(void)
 645{
 646        leaving = 1;
 647        wake_up(&thr_wq);
 648        down(&thread_sem);
 649#ifdef CONFIG_DEVFS_FS
 650        unregister_mtd_user(&notifier);
 651        devfs_unregister(devfs_dir_handle);
 652        devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME);
 653#else
 654        unregister_blkdev(MAJOR_NR,DEVICE_NAME);
 655#endif
 656        blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
 657        blksize_size[MAJOR_NR] = NULL;
 658        blk_size[MAJOR_NR] = NULL;
 659}
 660
 661module_init(init_mtdblock);
 662module_exit(cleanup_mtdblock);
 663
 664
 665MODULE_LICENSE("GPL");
 666MODULE_AUTHOR("Nicolas Pitre <nico@cam.org> et al.");
 667MODULE_DESCRIPTION("Caching read/erase/writeback block device emulation access to MTD devices");
 668
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.