1 /* 2 * $Id: mtdchar.c,v 1.76 2005/11/07 11:14:20 gleixner Exp $ 3 * 4 * Character-device access to raw MTD devices. 5 * 6 */ 7 8 #include <linux/device.h> 9 #include <linux/fs.h> 10 #include <linux/mm.h> 11 #include <linux/err.h> 12 #include <linux/init.h> 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/slab.h> 16 #include <linux/sched.h> 17 #include <linux/smp_lock.h> 18 19 #include <linux/mtd/mtd.h> 20 #include <linux/mtd/compatmac.h> 21 22 #include <asm/uaccess.h> 23 24 static struct class *mtd_class; 25 26 static void mtd_notify_add(struct mtd_info* mtd) 27 { 28 if (!mtd) 29 return; 30 31 device_create_drvdata(mtd_class, NULL, 32 MKDEV(MTD_CHAR_MAJOR, mtd->index*2), 33 NULL, "mtd%d", mtd->index); 34 35 device_create_drvdata(mtd_class, NULL, 36 MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), 37 NULL, "mtd%dro", mtd->index); 38 } 39 40 static void mtd_notify_remove(struct mtd_info* mtd) 41 { 42 if (!mtd) 43 return; 44 45 device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); 46 device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1)); 47 } 48 49 static struct mtd_notifier notifier = { 50 .add = mtd_notify_add, 51 .remove = mtd_notify_remove, 52 }; 53 54 /* 55 * Data structure to hold the pointer to the mtd device as well 56 * as mode information ofr various use cases. 57 */ 58 struct mtd_file_info { 59 struct mtd_info *mtd; 60 enum mtd_file_modes mode; 61 }; 62 63 static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) 64 { 65 struct mtd_file_info *mfi = file->private_data; 66 struct mtd_info *mtd = mfi->mtd; 67 68 switch (orig) { 69 case SEEK_SET: 70 break; 71 case SEEK_CUR: 72 offset += file->f_pos; 73 break; 74 case SEEK_END: 75 offset += mtd->size; 76 break; 77 default: 78 return -EINVAL; 79 } 80 81 if (offset >= 0 && offset <= mtd->size) 82 return file->f_pos = offset; 83 84 return -EINVAL; 85 } 86 87 88 89 static int mtd_open(struct inode *inode, struct file *file) 90 { 91 int minor = iminor(inode); 92 int devnum = minor >> 1; 93 int ret = 0; 94 struct mtd_info *mtd; 95 struct mtd_file_info *mfi; 96 97 DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); 98 99 if (devnum >= MAX_MTD_DEVICES) 100 return -ENODEV; 101 102 /* You can't open the RO devices RW */ 103 if ((file->f_mode & 2) && (minor & 1)) 104 return -EACCES; 105 106 lock_kernel(); 107 mtd = get_mtd_device(NULL, devnum); 108 109 if (IS_ERR(mtd)) { 110 ret = PTR_ERR(mtd); 111 goto out; 112 } 113 114 if (MTD_ABSENT == mtd->type) { 115 put_mtd_device(mtd); 116 ret = -ENODEV; 117 goto out; 118 } 119 120 /* You can't open it RW if it's not a writeable device */ 121 if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { 122 put_mtd_device(mtd); 123 ret = -EACCES; 124 goto out; 125 } 126 127 mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); 128 if (!mfi) { 129 put_mtd_device(mtd); 130 ret = -ENOMEM; 131 goto out; 132 } 133 mfi->mtd = mtd; 134 file->private_data = mfi; 135 136 out: 137 unlock_kernel(); 138 return ret; 139 } /* mtd_open */ 140 141 /*====================================================================*/ 142 143 static int mtd_close(struct inode *inode, struct file *file) 144 { 145 struct mtd_file_info *mfi = file->private_data; 146 struct mtd_info *mtd = mfi->mtd; 147 148 DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); 149 150 /* Only sync if opened RW */ 151 if ((file->f_mode & 2) && mtd->sync) 152 mtd->sync(mtd); 153 154 put_mtd_device(mtd); 155 file->private_data = NULL; 156 kfree(mfi); 157 158 return 0; 159 } /* mtd_close */ 160 161 /* FIXME: This _really_ needs to die. In 2.5, we should lock the 162 userspace buffer down and use it directly with readv/writev. 163 */ 164 #define MAX_KMALLOC_SIZE 0x20000 165 166 static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) 167 { 168 struct mtd_file_info *mfi = file->private_data; 169 struct mtd_info *mtd = mfi->mtd; 170 size_t retlen=0; 171 size_t total_retlen=0; 172 int ret=0; 173 int len; 174 char *kbuf; 175 176 DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n"); 177 178 if (*ppos + count > mtd->size) 179 count = mtd->size - *ppos; 180 181 if (!count) 182 return 0; 183 184 /* FIXME: Use kiovec in 2.5 to lock down the user's buffers 185 and pass them directly to the MTD functions */ 186 187 if (count > MAX_KMALLOC_SIZE) 188 kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL); 189 else 190 kbuf=kmalloc(count, GFP_KERNEL); 191 192 if (!kbuf) 193 return -ENOMEM; 194 195 while (count) { 196 197 if (count > MAX_KMALLOC_SIZE) 198 len = MAX_KMALLOC_SIZE; 199 else 200 len = count; 201 202 switch (mfi->mode) { 203 case MTD_MODE_OTP_FACTORY: 204 ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); 205 break; 206 case MTD_MODE_OTP_USER: 207 ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); 208 break; 209 case MTD_MODE_RAW: 210 { 211 struct mtd_oob_ops ops; 212 213 ops.mode = MTD_OOB_RAW; 214 ops.datbuf = kbuf; 215 ops.oobbuf = NULL; 216 ops.len = len; 217 218 ret = mtd->read_oob(mtd, *ppos, &ops); 219 retlen = ops.retlen; 220 break; 221 } 222 default: 223 ret = mtd->read(mtd, *ppos, len, &retlen, kbuf); 224 } 225 /* Nand returns -EBADMSG on ecc errors, but it returns 226 * the data. For our userspace tools it is important 227 * to dump areas with ecc errors ! 228 * For kernel internal usage it also might return -EUCLEAN 229 * to signal the caller that a bitflip has occured and has 230 * been corrected by the ECC algorithm. 231 * Userspace software which accesses NAND this way 232 * must be aware of the fact that it deals with NAND 233 */ 234 if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) { 235 *ppos += retlen; 236 if (copy_to_user(buf, kbuf, retlen)) { 237 kfree(kbuf); 238 return -EFAULT; 239 } 240 else 241 total_retlen += retlen; 242 243 count -= retlen; 244 buf += retlen; 245 if (retlen == 0) 246 count = 0; 247 } 248 else { 249 kfree(kbuf); 250 return ret; 251 } 252 253 } 254 255 kfree(kbuf); 256 return total_retlen; 257 } /* mtd_read */ 258 259 static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) 260 { 261 struct mtd_file_info *mfi = file->private_data; 262 struct mtd_info *mtd = mfi->mtd; 263 char *kbuf; 264 size_t retlen; 265 size_t total_retlen=0; 266 int ret=0; 267 int len; 268 269 DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); 270 271 if (*ppos == mtd->size) 272 return -ENOSPC; 273 274 if (*ppos + count > mtd->size) 275 count = mtd->size - *ppos; 276 277 if (!count) 278 return 0; 279 280 if (count > MAX_KMALLOC_SIZE) 281 kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL); 282 else 283 kbuf=kmalloc(count, GFP_KERNEL); 284 285 if (!kbuf) 286 return -ENOMEM; 287 288 while (count) { 289 290 if (count > MAX_KMALLOC_SIZE) 291 len = MAX_KMALLOC_SIZE; 292 else 293 len = count; 294 295 if (copy_from_user(kbuf, buf, len)) { 296 kfree(kbuf); 297 return -EFAULT; 298 } 299 300 switch (mfi->mode) { 301 case MTD_MODE_OTP_FACTORY: 302 ret = -EROFS; 303 break; 304 case MTD_MODE_OTP_USER: 305 if (!mtd->write_user_prot_reg) { 306 ret = -EOPNOTSUPP; 307 break; 308 } 309 ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); 310 break; 311 312 case MTD_MODE_RAW: 313 { 314 struct mtd_oob_ops ops; 315 316 ops.mode = MTD_OOB_RAW; 317 ops.datbuf = kbuf; 318 ops.oobbuf = NULL; 319 ops.len = len; 320 321 ret = mtd->write_oob(mtd, *ppos, &ops); 322 retlen = ops.retlen; 323 break; 324 } 325 326 default: 327 ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); 328 } 329 if (!ret) { 330 *ppos += retlen; 331 total_retlen += retlen; 332 count -= retlen; 333 buf += retlen; 334 } 335 else { 336 kfree(kbuf); 337 return ret; 338 } 339 } 340 341 kfree(kbuf); 342 return total_retlen; 343 } /* mtd_write */ 344 345 /*====================================================================== 346 347 IOCTL calls for getting device parameters. 348 349 ======================================================================*/ 350 static void mtdchar_erase_callback (struct erase_info *instr) 351 { 352 wake_up((wait_queue_head_t *)instr->priv); 353 } 354 355 #if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) 356 static int otp_select_filemode(struct mtd_file_info *mfi, int mode) 357 { 358 struct mtd_info *mtd = mfi->mtd; 359 int ret = 0; 360 361 switch (mode) { 362 case MTD_OTP_FACTORY: 363 if (!mtd->read_fact_prot_reg) 364 ret = -EOPNOTSUPP; 365 else 366 mfi->mode = MTD_MODE_OTP_FACTORY; 367 break; 368 case MTD_OTP_USER: 369 if (!mtd->read_fact_prot_reg) 370 ret = -EOPNOTSUPP; 371 else 372 mfi->mode = MTD_MODE_OTP_USER; 373 break; 374 default: 375 ret = -EINVAL; 376 case MTD_OTP_OFF: 377 break; 378 } 379 return ret; 380 } 381 #else 382 # define otp_select_filemode(f,m) -EOPNOTSUPP 383 #endif 384 385 static int mtd_ioctl(struct inode *inode, struct file *file, 386 u_int cmd, u_long arg) 387 { 388 struct mtd_file_info *mfi = file->private_data; 389 struct mtd_info *mtd = mfi->mtd; 390 void __user *argp = (void __user *)arg; 391 int ret = 0; 392 u_long size; 393 struct mtd_info_user info; 394 395 DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); 396 397 size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; 398 if (cmd & IOC_IN) { 399 if (!access_ok(VERIFY_READ, argp, size)) 400 return -EFAULT; 401 } 402 if (cmd & IOC_OUT) { 403 if (!access_ok(VERIFY_WRITE, argp, size)) 404 return -EFAULT; 405 } 406 407 switch (cmd) { 408 case MEMGETREGIONCOUNT: 409 if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int))) 410 return -EFAULT; 411 break; 412 413 case MEMGETREGIONINFO: 414 { 415 struct region_info_user ur; 416 417 if (copy_from_user(&ur, argp, sizeof(struct region_info_user))) 418 return -EFAULT; 419 420 if (ur.regionindex >= mtd->numeraseregions) 421 return -EINVAL; 422 if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]), 423 sizeof(struct mtd_erase_region_info))) 424 return -EFAULT; 425 break; 426 } 427 428 case MEMGETINFO: 429 info.type = mtd->type; 430 info.flags = mtd->flags; 431 info.size = mtd->size; 432 info.erasesize = mtd->erasesize; 433 info.writesize = mtd->writesize; 434 info.oobsize = mtd->oobsize; 435 /* The below fields are obsolete */ 436 info.ecctype = -1; 437 info.eccsize = 0; 438 if (copy_to_user(argp, &info, sizeof(struct mtd_info_user))) 439 return -EFAULT; 440 break; 441 442 case MEMERASE: 443 { 444 struct erase_info *erase; 445 446 if(!(file->f_mode & 2)) 447 return -EPERM; 448 449 erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL); 450 if (!erase) 451 ret = -ENOMEM; 452 else { 453 wait_queue_head_t waitq; 454 DECLARE_WAITQUEUE(wait, current); 455 456 init_waitqueue_head(&waitq); 457 458 if (copy_from_user(&erase->addr, argp, 459 sizeof(struct erase_info_user))) { 460 kfree(erase); 461 return -EFAULT; 462 } 463 erase->mtd = mtd; 464 erase->callback = mtdchar_erase_callback; 465 erase->priv = (unsigned long)&waitq; 466 467 /* 468 FIXME: Allow INTERRUPTIBLE. Which means 469 not having the wait_queue head on the stack. 470 471 If the wq_head is on the stack, and we 472 leave because we got interrupted, then the 473 wq_head is no longer there when the 474 callback routine tries to wake us up. 475 */ 476 ret = mtd->erase(mtd, erase); 477 if (!ret) { 478 set_current_state(TASK_UNINTERRUPTIBLE); 479 add_wait_queue(&waitq, &wait); 480 if (erase->state != MTD_ERASE_DONE && 481 erase->state != MTD_ERASE_FAILED) 482 schedule(); 483 remove_wait_queue(&waitq, &wait); 484 set_current_state(TASK_RUNNING); 485 486 ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0; 487 } 488 kfree(erase); 489 } 490 break; 491 } 492 493 case MEMWRITEOOB: 494 { 495 struct mtd_oob_buf buf; 496 struct mtd_oob_ops ops; 497 uint32_t retlen; 498 499 if(!(file->f_mode & 2)) 500 return -EPERM; 501 502 if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) 503 return -EFAULT; 504 505 if (buf.length > 4096) 506 return -EINVAL; 507 508 if (!mtd->write_oob) 509 ret = -EOPNOTSUPP; 510 else 511 ret = access_ok(VERIFY_READ, buf.ptr, 512 buf.length) ? 0 : EFAULT; 513 514 if (ret) 515 return ret; 516 517 ops.ooblen = buf.length; 518 ops.ooboffs = buf.start & (mtd->oobsize - 1); 519 ops.datbuf = NULL; 520 ops.mode = MTD_OOB_PLACE; 521 522 if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) 523 return -EINVAL; 524 525 ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); 526 if (!ops.oobbuf) 527 return -ENOMEM; 528 529 if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) { 530 kfree(ops.oobbuf); 531 return -EFAULT; 532 } 533 534 buf.start &= ~(mtd->oobsize - 1); 535 ret = mtd->write_oob(mtd, buf.start, &ops); 536 537 if (ops.oobretlen > 0xFFFFFFFFU) 538 ret = -EOVERFLOW; 539 retlen = ops.oobretlen; 540 if (copy_to_user(&((struct mtd_oob_buf *)argp)->length, 541 &retlen, sizeof(buf.length))) 542 ret = -EFAULT; 543 544 kfree(ops.oobbuf); 545 break; 546 547 } 548 549 case MEMREADOOB: 550 { 551 struct mtd_oob_buf buf; 552 struct mtd_oob_ops ops; 553 554 if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) 555 return -EFAULT; 556 557 if (buf.length > 4096) 558 return -EINVAL; 559 560 if (!mtd->read_oob) 561 ret = -EOPNOTSUPP; 562 else 563 ret = access_ok(VERIFY_WRITE, buf.ptr, 564 buf.length) ? 0 : -EFAULT; 565 if (ret) 566 return ret; 567 568 ops.ooblen = buf.length; 569 ops.ooboffs = buf.start & (mtd->oobsize - 1); 570 ops.datbuf = NULL; 571 ops.mode = MTD_OOB_PLACE; 572 573 if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) 574 return -EINVAL; 575 576 ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); 577 if (!ops.oobbuf) 578 return -ENOMEM; 579 580 buf.start &= ~(mtd->oobsize - 1); 581 ret = mtd->read_oob(mtd, buf.start, &ops); 582 583 if (put_user(ops.oobretlen, (uint32_t __user *)argp)) 584 ret = -EFAULT; 585 else if (ops.oobretlen && copy_to_user(buf.ptr, ops.oobbuf, 586 ops.oobretlen)) 587 ret = -EFAULT; 588 589 kfree(ops.oobbuf); 590 break; 591 } 592 593 case MEMLOCK: 594 { 595 struct erase_info_user info; 596 597 if (copy_from_user(&info, argp, sizeof(info))) 598 return -EFAULT; 599 600 if (!mtd->lock) 601 ret = -EOPNOTSUPP; 602 else 603 ret = mtd->lock(mtd, info.start, info.length); 604 break; 605 } 606 607 case MEMUNLOCK: 608 { 609 struct erase_info_user info; 610 611 if (copy_from_user(&info, argp, sizeof(info))) 612 return -EFAULT; 613 614 if (!mtd->unlock) 615 ret = -EOPNOTSUPP; 616 else 617 ret = mtd->unlock(mtd, info.start, info.length); 618 break; 619 } 620 621 /* Legacy interface */ 622 case MEMGETOOBSEL: 623 { 624 struct nand_oobinfo oi; 625 626 if (!mtd->ecclayout) 627 return -EOPNOTSUPP; 628 if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos)) 629 return -EINVAL; 630 631 oi.useecc = MTD_NANDECC_AUTOPLACE; 632 memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos)); 633 memcpy(&oi.oobfree, mtd->ecclayout->oobfree, 634 sizeof(oi.oobfree)); 635 oi.eccbytes = mtd->ecclayout->eccbytes; 636 637 if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo))) 638 return -EFAULT; 639 break; 640 } 641 642 case MEMGETBADBLOCK: 643 { 644 loff_t offs; 645 646 if (copy_from_user(&offs, argp, sizeof(loff_t))) 647 return -EFAULT; 648 if (!mtd->block_isbad) 649 ret = -EOPNOTSUPP; 650 else 651 return mtd->block_isbad(mtd, offs); 652 break; 653 } 654 655 case MEMSETBADBLOCK: 656 { 657 loff_t offs; 658 659 if (copy_from_user(&offs, argp, sizeof(loff_t))) 660 return -EFAULT; 661 if (!mtd->block_markbad) 662 ret = -EOPNOTSUPP; 663 else 664 return mtd->block_markbad(mtd, offs); 665 break; 666 } 667 668 #if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) 669 case OTPSELECT: 670 { 671 int mode; 672 if (copy_from_user(&mode, argp, sizeof(int))) 673 return -EFAULT; 674 675 mfi->mode = MTD_MODE_NORMAL; 676 677 ret = otp_select_filemode(mfi, mode); 678 679 file->f_pos = 0; 680 break; 681 } 682 683 case OTPGETREGIONCOUNT: 684 case OTPGETREGIONINFO: 685 { 686 struct otp_info *buf = kmalloc(4096, GFP_KERNEL); 687 if (!buf) 688 return -ENOMEM; 689 ret = -EOPNOTSUPP; 690 switch (mfi->mode) { 691 case MTD_MODE_OTP_FACTORY: 692 if (mtd->get_fact_prot_info) 693 ret = mtd->get_fact_prot_info(mtd, buf, 4096); 694 break; 695 case MTD_MODE_OTP_USER: 696 if (mtd->get_user_prot_info) 697 ret = mtd->get_user_prot_info(mtd, buf, 4096); 698 break; 699 default: 700 break; 701 } 702 if (ret >= 0) { 703 if (cmd == OTPGETREGIONCOUNT) { 704 int nbr = ret / sizeof(struct otp_info); 705 ret = copy_to_user(argp, &nbr, sizeof(int)); 706 } else 707 ret = copy_to_user(argp, buf, ret); 708 if (ret) 709 ret = -EFAULT; 710 } 711 kfree(buf); 712 break; 713 } 714 715 case OTPLOCK: 716 { 717 struct otp_info info; 718 719 if (mfi->mode != MTD_MODE_OTP_USER) 720 return -EINVAL; 721 if (copy_from_user(&info, argp, sizeof(info))) 722 return -EFAULT; 723 if (!mtd->lock_user_prot_reg) 724 return -EOPNOTSUPP; 725 ret = mtd->lock_user_prot_reg(mtd, info.start, info.length); 726 break; 727 } 728 #endif 729 730 case ECCGETLAYOUT: 731 { 732 if (!mtd->ecclayout) 733 return -EOPNOTSUPP; 734 735 if (copy_to_user(argp, mtd->ecclayout, 736 sizeof(struct nand_ecclayout))) 737 return -EFAULT; 738 break; 739 } 740 741 case ECCGETSTATS: 742 { 743 if (copy_to_user(argp, &mtd->ecc_stats, 744 sizeof(struct mtd_ecc_stats))) 745 return -EFAULT; 746 break; 747 } 748 749 case MTDFILEMODE: 750 { 751 mfi->mode = 0; 752 753 switch(arg) { 754 case MTD_MODE_OTP_FACTORY: 755 case MTD_MODE_OTP_USER: 756 ret = otp_select_filemode(mfi, arg); 757 break; 758 759 case MTD_MODE_RAW: 760 if (!mtd->read_oob || !mtd->write_oob) 761 return -EOPNOTSUPP; 762 mfi->mode = arg; 763 764 case MTD_MODE_NORMAL: 765 break; 766 default: 767 ret = -EINVAL; 768 } 769 file->f_pos = 0; 770 break; 771 } 772 773 default: 774 ret = -ENOTTY; 775 } 776 777 return ret; 778 } /* memory_ioctl */ 779 780 static const struct file_operations mtd_fops = { 781 .owner = THIS_MODULE, 782 .llseek = mtd_lseek, 783 .read = mtd_read, 784 .write = mtd_write, 785 .ioctl = mtd_ioctl, 786 .open = mtd_open, 787 .release = mtd_close, 788 }; 789 790 static int __init init_mtdchar(void) 791 { 792 if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { 793 printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", 794 MTD_CHAR_MAJOR); 795 return -EAGAIN; 796 } 797 798 mtd_class = class_create(THIS_MODULE, "mtd"); 799 800 if (IS_ERR(mtd_class)) { 801 printk(KERN_ERR "Error creating mtd class.\n"); 802 unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); 803 return PTR_ERR(mtd_class); 804 } 805 806 register_mtd_user(¬ifier); 807 return 0; 808 } 809 810 static void __exit cleanup_mtdchar(void) 811 { 812 unregister_mtd_user(¬ifier); 813 class_destroy(mtd_class); 814 unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); 815 } 816 817 module_init(init_mtdchar); 818 module_exit(cleanup_mtdchar); 819 820 821 MODULE_LICENSE("GPL"); 822 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 823 MODULE_DESCRIPTION("Direct character-device access to MTD devices"); 824