linux-old/drivers/mtd/mtdchar.c
<<
>>
Prefs
   1/*
   2 * $Id: mtdchar.c,v 1.44 2001/10/02 15:05:11 dwmw2 Exp $
   3 *
   4 * Character-device access to raw MTD devices.
   5 * Pure 2.4 version - compatibility cruft removed to mtdchar-compat.c
   6 *
   7 */
   8
   9#include <linux/config.h>
  10#include <linux/kernel.h>
  11#include <linux/module.h>
  12#include <linux/mtd/mtd.h>
  13#include <linux/slab.h>
  14
  15#ifdef CONFIG_DEVFS_FS
  16#include <linux/devfs_fs_kernel.h>
  17static void mtd_notify_add(struct mtd_info* mtd);
  18static void mtd_notify_remove(struct mtd_info* mtd);
  19
  20static struct mtd_notifier notifier = {
  21        add:    mtd_notify_add,
  22        remove: mtd_notify_remove,
  23};
  24
  25static devfs_handle_t devfs_dir_handle;
  26static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES];
  27static devfs_handle_t devfs_ro_handle[MAX_MTD_DEVICES];
  28#endif
  29
  30static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
  31{
  32        struct mtd_info *mtd=(struct mtd_info *)file->private_data;
  33
  34        switch (orig) {
  35        case 0:
  36                /* SEEK_SET */
  37                file->f_pos = offset;
  38                break;
  39        case 1:
  40                /* SEEK_CUR */
  41                file->f_pos += offset;
  42                break;
  43        case 2:
  44                /* SEEK_END */
  45                file->f_pos =mtd->size + offset;
  46                break;
  47        default:
  48                return -EINVAL;
  49        }
  50
  51        if (file->f_pos < 0)
  52                file->f_pos = 0;
  53        else if (file->f_pos >= mtd->size)
  54                file->f_pos = mtd->size - 1;
  55
  56        return file->f_pos;
  57}
  58
  59
  60
  61static int mtd_open(struct inode *inode, struct file *file)
  62{
  63        int minor = MINOR(inode->i_rdev);
  64        int devnum = minor >> 1;
  65        struct mtd_info *mtd;
  66
  67        DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
  68
  69        if (devnum >= MAX_MTD_DEVICES)
  70                return -ENODEV;
  71
  72        /* You can't open the RO devices RW */
  73        if ((file->f_mode & 2) && (minor & 1))
  74                return -EACCES;
  75
  76        mtd = get_mtd_device(NULL, devnum);
  77        
  78        if (!mtd)
  79                return -ENODEV;
  80        
  81        if (MTD_ABSENT == mtd->type) {
  82                put_mtd_device(mtd);
  83                return -ENODEV;
  84        }
  85
  86        file->private_data = mtd;
  87                
  88        /* You can't open it RW if it's not a writeable device */
  89        if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
  90                put_mtd_device(mtd);
  91                return -EACCES;
  92        }
  93                
  94        return 0;
  95} /* mtd_open */
  96
  97/*====================================================================*/
  98
  99static int mtd_close(struct inode *inode, struct file *file)
 100{
 101        struct mtd_info *mtd;
 102
 103        DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
 104
 105        mtd = (struct mtd_info *)file->private_data;
 106        
 107        if (mtd->sync)
 108                mtd->sync(mtd);
 109        
 110        put_mtd_device(mtd);
 111
 112        return 0;
 113} /* mtd_close */
 114
 115/* FIXME: This _really_ needs to die. In 2.5, we should lock the
 116   userspace buffer down and use it directly with readv/writev.
 117*/
 118#define MAX_KMALLOC_SIZE 0x20000
 119
 120static ssize_t mtd_read(struct file *file, char *buf, size_t count,loff_t *ppos)
 121{
 122        struct mtd_info *mtd = (struct mtd_info *)file->private_data;
 123        size_t retlen=0;
 124        size_t total_retlen=0;
 125        int ret=0;
 126        int len;
 127        char *kbuf;
 128        
 129        DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
 130
 131        if (*ppos + count > mtd->size)
 132                count = mtd->size - *ppos;
 133
 134        if (!count)
 135                return 0;
 136        
 137        /* FIXME: Use kiovec in 2.5 to lock down the user's buffers
 138           and pass them directly to the MTD functions */
 139        while (count) {
 140                if (count > MAX_KMALLOC_SIZE) 
 141                        len = MAX_KMALLOC_SIZE;
 142                else
 143                        len = count;
 144
 145                kbuf=kmalloc(len,GFP_KERNEL);
 146                if (!kbuf)
 147                        return -ENOMEM;
 148                
 149                ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf);
 150                if (!ret) {
 151                        *ppos += retlen;
 152                        if (copy_to_user(buf, kbuf, retlen)) {
 153                                kfree(kbuf);
 154                                return -EFAULT;
 155                        }
 156                        else
 157                                total_retlen += retlen;
 158
 159                        count -= retlen;
 160                        buf += retlen;
 161                }
 162                else {
 163                        kfree(kbuf);
 164                        return ret;
 165                }
 166                
 167                kfree(kbuf);
 168        }
 169        
 170        return total_retlen;
 171} /* mtd_read */
 172
 173static ssize_t mtd_write(struct file *file, const char *buf, size_t count,loff_t *ppos)
 174{
 175        struct mtd_info *mtd = (struct mtd_info *)file->private_data;
 176        char *kbuf;
 177        size_t retlen;
 178        size_t total_retlen=0;
 179        int ret=0;
 180        int len;
 181
 182        DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
 183        
 184        if (*ppos == mtd->size)
 185                return -ENOSPC;
 186        
 187        if (*ppos + count > mtd->size)
 188                count = mtd->size - *ppos;
 189
 190        if (!count)
 191                return 0;
 192
 193        while (count) {
 194                if (count > MAX_KMALLOC_SIZE) 
 195                        len = MAX_KMALLOC_SIZE;
 196                else
 197                        len = count;
 198
 199                kbuf=kmalloc(len,GFP_KERNEL);
 200                if (!kbuf) {
 201                        printk("kmalloc is null\n");
 202                        return -ENOMEM;
 203                }
 204
 205                if (copy_from_user(kbuf, buf, len)) {
 206                        kfree(kbuf);
 207                        return -EFAULT;
 208                }
 209                
 210                ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
 211                if (!ret) {
 212                        *ppos += retlen;
 213                        total_retlen += retlen;
 214                        count -= retlen;
 215                        buf += retlen;
 216                }
 217                else {
 218                        kfree(kbuf);
 219                        return ret;
 220                }
 221                
 222                kfree(kbuf);
 223        }
 224
 225        return total_retlen;
 226} /* mtd_write */
 227
 228/*======================================================================
 229
 230    IOCTL calls for getting device parameters.
 231
 232======================================================================*/
 233static void mtd_erase_callback (struct erase_info *instr)
 234{
 235        wake_up((wait_queue_head_t *)instr->priv);
 236}
 237
 238static int mtd_ioctl(struct inode *inode, struct file *file,
 239                     u_int cmd, u_long arg)
 240{
 241        struct mtd_info *mtd = (struct mtd_info *)file->private_data;
 242        int ret = 0;
 243        u_long size;
 244        
 245        DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
 246
 247        size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
 248        if (cmd & IOC_IN) {
 249                ret = verify_area(VERIFY_READ, (char *)arg, size);
 250                if (ret) return ret;
 251        }
 252        if (cmd & IOC_OUT) {
 253                ret = verify_area(VERIFY_WRITE, (char *)arg, size);
 254                if (ret) return ret;
 255        }
 256        
 257        switch (cmd) {
 258        case MEMGETREGIONCOUNT:
 259                if (copy_to_user((int *) arg, &(mtd->numeraseregions), sizeof(int)))
 260                        return -EFAULT;
 261                break;
 262
 263        case MEMGETREGIONINFO:
 264        {
 265                struct region_info_user ur;
 266
 267                if (copy_from_user(     &ur, 
 268                                        (struct region_info_user *)arg, 
 269                                        sizeof(struct region_info_user))) {
 270                        return -EFAULT;
 271                }
 272
 273                if (ur.regionindex >= mtd->numeraseregions)
 274                        return -EINVAL;
 275                if (copy_to_user((struct mtd_erase_region_info *) arg, 
 276                                &(mtd->eraseregions[ur.regionindex]),
 277                                sizeof(struct mtd_erase_region_info)))
 278                        return -EFAULT;
 279                break;
 280        }
 281
 282        case MEMGETINFO:
 283                if (copy_to_user((struct mtd_info *)arg, mtd,
 284                                 sizeof(struct mtd_info_user)))
 285                        return -EFAULT;
 286                break;
 287
 288        case MEMERASE:
 289        {
 290                struct erase_info *erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL);
 291                if (!erase)
 292                        ret = -ENOMEM;
 293                else {
 294                        wait_queue_head_t waitq;
 295                        DECLARE_WAITQUEUE(wait, current);
 296
 297                        init_waitqueue_head(&waitq);
 298
 299                        memset (erase,0,sizeof(struct erase_info));
 300                        if (copy_from_user(&erase->addr, (u_long *)arg,
 301                                           2 * sizeof(u_long))) {
 302                                kfree(erase);
 303                                return -EFAULT;
 304                        }
 305                        erase->mtd = mtd;
 306                        erase->callback = mtd_erase_callback;
 307                        erase->priv = (unsigned long)&waitq;
 308                        
 309                        /*
 310                          FIXME: Allow INTERRUPTIBLE. Which means
 311                          not having the wait_queue head on the stack.
 312                          
 313                          If the wq_head is on the stack, and we
 314                          leave because we got interrupted, then the
 315                          wq_head is no longer there when the
 316                          callback routine tries to wake us up.
 317                        */
 318                        ret = mtd->erase(mtd, erase);
 319                        if (!ret) {
 320                                set_current_state(TASK_UNINTERRUPTIBLE);
 321                                add_wait_queue(&waitq, &wait);
 322                                if (erase->state != MTD_ERASE_DONE &&
 323                                    erase->state != MTD_ERASE_FAILED)
 324                                        schedule();
 325                                remove_wait_queue(&waitq, &wait);
 326                                set_current_state(TASK_RUNNING);
 327
 328                                ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
 329                        }
 330                        kfree(erase);
 331                }
 332                break;
 333        }
 334
 335        case MEMWRITEOOB:
 336        {
 337                struct mtd_oob_buf buf;
 338                void *databuf;
 339                ssize_t retlen;
 340                
 341                if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)))
 342                        return -EFAULT;
 343                
 344                if (buf.length > 0x4096)
 345                        return -EINVAL;
 346
 347                if (!mtd->write_oob)
 348                        ret = -EOPNOTSUPP;
 349                else
 350                        ret = verify_area(VERIFY_READ, (char *)buf.ptr, buf.length);
 351
 352                if (ret)
 353                        return ret;
 354
 355                databuf = kmalloc(buf.length, GFP_KERNEL);
 356                if (!databuf)
 357                        return -ENOMEM;
 358                
 359                if (copy_from_user(databuf, buf.ptr, buf.length)) {
 360                        kfree(databuf);
 361                        return -EFAULT;
 362                }
 363
 364                ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf);
 365
 366                if (copy_to_user((void *)arg + sizeof(u_int32_t), &retlen, sizeof(u_int32_t)))
 367                        ret = -EFAULT;
 368
 369                kfree(databuf);
 370                break;
 371
 372        }
 373
 374        case MEMREADOOB:
 375        {
 376                struct mtd_oob_buf buf;
 377                void *databuf;
 378                ssize_t retlen;
 379
 380                if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)))
 381                        return -EFAULT;
 382                
 383                if (buf.length > 0x4096)
 384                        return -EINVAL;
 385
 386                if (!mtd->read_oob)
 387                        ret = -EOPNOTSUPP;
 388                else
 389                        ret = verify_area(VERIFY_WRITE, (char *)buf.ptr, buf.length);
 390
 391                if (ret)
 392                        return ret;
 393
 394                databuf = kmalloc(buf.length, GFP_KERNEL);
 395                if (!databuf)
 396                        return -ENOMEM;
 397                
 398                ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf);
 399
 400                if (copy_to_user((void *)arg + sizeof(u_int32_t), &retlen, sizeof(u_int32_t)))
 401                        ret = -EFAULT;
 402                else if (retlen && copy_to_user(buf.ptr, databuf, retlen))
 403                        ret = -EFAULT;
 404                
 405                kfree(databuf);
 406                break;
 407        }
 408
 409        case MEMLOCK:
 410        {
 411                unsigned long adrs[2];
 412
 413                if (copy_from_user(adrs ,(void *)arg, 2* sizeof(unsigned long)))
 414                        return -EFAULT;
 415
 416                if (!mtd->lock)
 417                        ret = -EOPNOTSUPP;
 418                else
 419                        ret = mtd->lock(mtd, adrs[0], adrs[1]);
 420                break;
 421        }
 422
 423        case MEMUNLOCK:
 424        {
 425                unsigned long adrs[2];
 426
 427                if (copy_from_user(adrs, (void *)arg, 2* sizeof(unsigned long)))
 428                        return -EFAULT;
 429
 430                if (!mtd->unlock)
 431                        ret = -EOPNOTSUPP;
 432                else
 433                        ret = mtd->unlock(mtd, adrs[0], adrs[1]);
 434                break;
 435        }
 436
 437                
 438        default:
 439                DEBUG(MTD_DEBUG_LEVEL0, "Invalid ioctl %x (MEMGETINFO = %x)\n", cmd, MEMGETINFO);
 440                ret = -ENOTTY;
 441        }
 442
 443        return ret;
 444} /* memory_ioctl */
 445
 446static struct file_operations mtd_fops = {
 447        owner:          THIS_MODULE,
 448        llseek:         mtd_lseek,      /* lseek */
 449        read:           mtd_read,       /* read */
 450        write:          mtd_write,      /* write */
 451        ioctl:          mtd_ioctl,      /* ioctl */
 452        open:           mtd_open,       /* open */
 453        release:        mtd_close,      /* release */
 454};
 455
 456
 457#ifdef CONFIG_DEVFS_FS
 458/* Notification that a new device has been added. Create the devfs entry for
 459 * it. */
 460
 461static void mtd_notify_add(struct mtd_info* mtd)
 462{
 463        char name[8];
 464
 465        if (!mtd)
 466                return;
 467
 468        sprintf(name, "%d", mtd->index);
 469        devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
 470                        DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2,
 471                        S_IFCHR | S_IRUGO | S_IWUGO,
 472                        &mtd_fops, NULL);
 473
 474        sprintf(name, "%dro", mtd->index);
 475        devfs_ro_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
 476                        DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2+1,
 477                        S_IFCHR | S_IRUGO | S_IWUGO,
 478                        &mtd_fops, NULL);
 479}
 480
 481static void mtd_notify_remove(struct mtd_info* mtd)
 482{
 483        if (!mtd)
 484                return;
 485
 486        devfs_unregister(devfs_rw_handle[mtd->index]);
 487        devfs_unregister(devfs_ro_handle[mtd->index]);
 488}
 489#endif
 490
 491static int __init init_mtdchar(void)
 492{
 493#ifdef CONFIG_DEVFS_FS
 494        if (devfs_register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops))
 495        {
 496                printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
 497                       MTD_CHAR_MAJOR);
 498                return -EAGAIN;
 499        }
 500
 501        devfs_dir_handle = devfs_mk_dir(NULL, "mtd", NULL);
 502
 503        register_mtd_user(&notifier);
 504#else
 505        if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops))
 506        {
 507                printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
 508                       MTD_CHAR_MAJOR);
 509                return -EAGAIN;
 510        }
 511#endif
 512
 513        return 0;
 514}
 515
 516static void __exit cleanup_mtdchar(void)
 517{
 518#ifdef CONFIG_DEVFS_FS
 519        unregister_mtd_user(&notifier);
 520        devfs_unregister(devfs_dir_handle);
 521        devfs_unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
 522#else
 523        unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
 524#endif
 525}
 526
 527module_init(init_mtdchar);
 528module_exit(cleanup_mtdchar);
 529
 530
 531MODULE_LICENSE("GPL");
 532MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
 533MODULE_DESCRIPTION("Direct character-device access to MTD devices");
 534
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.