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