diff options
Diffstat (limited to 'drivers/mtd/mtdchar.c')
-rw-r--r-- | drivers/mtd/mtdchar.c | 176 |
1 files changed, 143 insertions, 33 deletions
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index 510ad78312cc..1ed602a0f24c 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * $Id: mtdchar.c,v 1.66 2005/01/05 18:05:11 dwmw2 Exp $ | 2 | * $Id: mtdchar.c,v 1.73 2005/07/04 17:36:41 gleixner Exp $ |
3 | * | 3 | * |
4 | * Character-device access to raw MTD devices. | 4 | * Character-device access to raw MTD devices. |
5 | * | 5 | * |
@@ -15,27 +15,30 @@ | |||
15 | #include <linux/fs.h> | 15 | #include <linux/fs.h> |
16 | #include <asm/uaccess.h> | 16 | #include <asm/uaccess.h> |
17 | 17 | ||
18 | #ifdef CONFIG_DEVFS_FS | 18 | #include <linux/device.h> |
19 | #include <linux/devfs_fs_kernel.h> | 19 | |
20 | static struct class *mtd_class; | ||
20 | 21 | ||
21 | static void mtd_notify_add(struct mtd_info* mtd) | 22 | static void mtd_notify_add(struct mtd_info* mtd) |
22 | { | 23 | { |
23 | if (!mtd) | 24 | if (!mtd) |
24 | return; | 25 | return; |
25 | 26 | ||
26 | devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2), | 27 | class_device_create(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), |
27 | S_IFCHR | S_IRUGO | S_IWUGO, "mtd/%d", mtd->index); | 28 | NULL, "mtd%d", mtd->index); |
28 | 29 | ||
29 | devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), | 30 | class_device_create(mtd_class, |
30 | S_IFCHR | S_IRUGO, "mtd/%dro", mtd->index); | 31 | MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), |
32 | NULL, "mtd%dro", mtd->index); | ||
31 | } | 33 | } |
32 | 34 | ||
33 | static void mtd_notify_remove(struct mtd_info* mtd) | 35 | static void mtd_notify_remove(struct mtd_info* mtd) |
34 | { | 36 | { |
35 | if (!mtd) | 37 | if (!mtd) |
36 | return; | 38 | return; |
37 | devfs_remove("mtd/%d", mtd->index); | 39 | |
38 | devfs_remove("mtd/%dro", mtd->index); | 40 | class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); |
41 | class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1)); | ||
39 | } | 42 | } |
40 | 43 | ||
41 | static struct mtd_notifier notifier = { | 44 | static struct mtd_notifier notifier = { |
@@ -43,25 +46,25 @@ static struct mtd_notifier notifier = { | |||
43 | .remove = mtd_notify_remove, | 46 | .remove = mtd_notify_remove, |
44 | }; | 47 | }; |
45 | 48 | ||
46 | static inline void mtdchar_devfs_init(void) | 49 | /* |
47 | { | 50 | * We use file->private_data to store a pointer to the MTDdevice. |
48 | devfs_mk_dir("mtd"); | 51 | * Since alighment is at least 32 bits, we have 2 bits free for OTP |
49 | register_mtd_user(¬ifier); | 52 | * modes as well. |
50 | } | 53 | */ |
51 | 54 | ||
52 | static inline void mtdchar_devfs_exit(void) | 55 | #define TO_MTD(file) (struct mtd_info *)((long)((file)->private_data) & ~3L) |
53 | { | 56 | |
54 | unregister_mtd_user(¬ifier); | 57 | #define MTD_MODE_OTP_FACT 1 |
55 | devfs_remove("mtd"); | 58 | #define MTD_MODE_OTP_USER 2 |
56 | } | 59 | #define MTD_MODE(file) ((long)((file)->private_data) & 3) |
57 | #else /* !DEVFS */ | 60 | |
58 | #define mtdchar_devfs_init() do { } while(0) | 61 | #define SET_MTD_MODE(file, mode) \ |
59 | #define mtdchar_devfs_exit() do { } while(0) | 62 | do { long __p = (long)((file)->private_data); \ |
60 | #endif | 63 | (file)->private_data = (void *)((__p & ~3L) | mode); } while (0) |
61 | 64 | ||
62 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) | 65 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) |
63 | { | 66 | { |
64 | struct mtd_info *mtd = file->private_data; | 67 | struct mtd_info *mtd = TO_MTD(file); |
65 | 68 | ||
66 | switch (orig) { | 69 | switch (orig) { |
67 | case 0: | 70 | case 0: |
@@ -134,7 +137,7 @@ static int mtd_close(struct inode *inode, struct file *file) | |||
134 | 137 | ||
135 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); | 138 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); |
136 | 139 | ||
137 | mtd = file->private_data; | 140 | mtd = TO_MTD(file); |
138 | 141 | ||
139 | if (mtd->sync) | 142 | if (mtd->sync) |
140 | mtd->sync(mtd); | 143 | mtd->sync(mtd); |
@@ -151,7 +154,7 @@ static int mtd_close(struct inode *inode, struct file *file) | |||
151 | 154 | ||
152 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) | 155 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) |
153 | { | 156 | { |
154 | struct mtd_info *mtd = file->private_data; | 157 | struct mtd_info *mtd = TO_MTD(file); |
155 | size_t retlen=0; | 158 | size_t retlen=0; |
156 | size_t total_retlen=0; | 159 | size_t total_retlen=0; |
157 | int ret=0; | 160 | int ret=0; |
@@ -178,7 +181,16 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t | |||
178 | if (!kbuf) | 181 | if (!kbuf) |
179 | return -ENOMEM; | 182 | return -ENOMEM; |
180 | 183 | ||
181 | ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf); | 184 | switch (MTD_MODE(file)) { |
185 | case MTD_MODE_OTP_FACT: | ||
186 | ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); | ||
187 | break; | ||
188 | case MTD_MODE_OTP_USER: | ||
189 | ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | ||
190 | break; | ||
191 | default: | ||
192 | ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf); | ||
193 | } | ||
182 | /* Nand returns -EBADMSG on ecc errors, but it returns | 194 | /* Nand returns -EBADMSG on ecc errors, but it returns |
183 | * the data. For our userspace tools it is important | 195 | * the data. For our userspace tools it is important |
184 | * to dump areas with ecc errors ! | 196 | * to dump areas with ecc errors ! |
@@ -196,6 +208,8 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t | |||
196 | 208 | ||
197 | count -= retlen; | 209 | count -= retlen; |
198 | buf += retlen; | 210 | buf += retlen; |
211 | if (retlen == 0) | ||
212 | count = 0; | ||
199 | } | 213 | } |
200 | else { | 214 | else { |
201 | kfree(kbuf); | 215 | kfree(kbuf); |
@@ -210,7 +224,7 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t | |||
210 | 224 | ||
211 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) | 225 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) |
212 | { | 226 | { |
213 | struct mtd_info *mtd = file->private_data; | 227 | struct mtd_info *mtd = TO_MTD(file); |
214 | char *kbuf; | 228 | char *kbuf; |
215 | size_t retlen; | 229 | size_t retlen; |
216 | size_t total_retlen=0; | 230 | size_t total_retlen=0; |
@@ -245,7 +259,20 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count | |||
245 | return -EFAULT; | 259 | return -EFAULT; |
246 | } | 260 | } |
247 | 261 | ||
248 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); | 262 | switch (MTD_MODE(file)) { |
263 | case MTD_MODE_OTP_FACT: | ||
264 | ret = -EROFS; | ||
265 | break; | ||
266 | case MTD_MODE_OTP_USER: | ||
267 | if (!mtd->write_user_prot_reg) { | ||
268 | ret = -EOPNOTSUPP; | ||
269 | break; | ||
270 | } | ||
271 | ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | ||
272 | break; | ||
273 | default: | ||
274 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); | ||
275 | } | ||
249 | if (!ret) { | 276 | if (!ret) { |
250 | *ppos += retlen; | 277 | *ppos += retlen; |
251 | total_retlen += retlen; | 278 | total_retlen += retlen; |
@@ -276,7 +303,7 @@ static void mtdchar_erase_callback (struct erase_info *instr) | |||
276 | static int mtd_ioctl(struct inode *inode, struct file *file, | 303 | static int mtd_ioctl(struct inode *inode, struct file *file, |
277 | u_int cmd, u_long arg) | 304 | u_int cmd, u_long arg) |
278 | { | 305 | { |
279 | struct mtd_info *mtd = file->private_data; | 306 | struct mtd_info *mtd = TO_MTD(file); |
280 | void __user *argp = (void __user *)arg; | 307 | void __user *argp = (void __user *)arg; |
281 | int ret = 0; | 308 | int ret = 0; |
282 | u_long size; | 309 | u_long size; |
@@ -518,6 +545,80 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
518 | break; | 545 | break; |
519 | } | 546 | } |
520 | 547 | ||
548 | #ifdef CONFIG_MTD_OTP | ||
549 | case OTPSELECT: | ||
550 | { | ||
551 | int mode; | ||
552 | if (copy_from_user(&mode, argp, sizeof(int))) | ||
553 | return -EFAULT; | ||
554 | SET_MTD_MODE(file, 0); | ||
555 | switch (mode) { | ||
556 | case MTD_OTP_FACTORY: | ||
557 | if (!mtd->read_fact_prot_reg) | ||
558 | ret = -EOPNOTSUPP; | ||
559 | else | ||
560 | SET_MTD_MODE(file, MTD_MODE_OTP_FACT); | ||
561 | break; | ||
562 | case MTD_OTP_USER: | ||
563 | if (!mtd->read_fact_prot_reg) | ||
564 | ret = -EOPNOTSUPP; | ||
565 | else | ||
566 | SET_MTD_MODE(file, MTD_MODE_OTP_USER); | ||
567 | break; | ||
568 | default: | ||
569 | ret = -EINVAL; | ||
570 | case MTD_OTP_OFF: | ||
571 | break; | ||
572 | } | ||
573 | file->f_pos = 0; | ||
574 | break; | ||
575 | } | ||
576 | |||
577 | case OTPGETREGIONCOUNT: | ||
578 | case OTPGETREGIONINFO: | ||
579 | { | ||
580 | struct otp_info *buf = kmalloc(4096, GFP_KERNEL); | ||
581 | if (!buf) | ||
582 | return -ENOMEM; | ||
583 | ret = -EOPNOTSUPP; | ||
584 | switch (MTD_MODE(file)) { | ||
585 | case MTD_MODE_OTP_FACT: | ||
586 | if (mtd->get_fact_prot_info) | ||
587 | ret = mtd->get_fact_prot_info(mtd, buf, 4096); | ||
588 | break; | ||
589 | case MTD_MODE_OTP_USER: | ||
590 | if (mtd->get_user_prot_info) | ||
591 | ret = mtd->get_user_prot_info(mtd, buf, 4096); | ||
592 | break; | ||
593 | } | ||
594 | if (ret >= 0) { | ||
595 | if (cmd == OTPGETREGIONCOUNT) { | ||
596 | int nbr = ret / sizeof(struct otp_info); | ||
597 | ret = copy_to_user(argp, &nbr, sizeof(int)); | ||
598 | } else | ||
599 | ret = copy_to_user(argp, buf, ret); | ||
600 | if (ret) | ||
601 | ret = -EFAULT; | ||
602 | } | ||
603 | kfree(buf); | ||
604 | break; | ||
605 | } | ||
606 | |||
607 | case OTPLOCK: | ||
608 | { | ||
609 | struct otp_info info; | ||
610 | |||
611 | if (MTD_MODE(file) != MTD_MODE_OTP_USER) | ||
612 | return -EINVAL; | ||
613 | if (copy_from_user(&info, argp, sizeof(info))) | ||
614 | return -EFAULT; | ||
615 | if (!mtd->lock_user_prot_reg) | ||
616 | return -EOPNOTSUPP; | ||
617 | ret = mtd->lock_user_prot_reg(mtd, info.start, info.length); | ||
618 | break; | ||
619 | } | ||
620 | #endif | ||
621 | |||
521 | default: | 622 | default: |
522 | ret = -ENOTTY; | 623 | ret = -ENOTTY; |
523 | } | 624 | } |
@@ -543,13 +644,22 @@ static int __init init_mtdchar(void) | |||
543 | return -EAGAIN; | 644 | return -EAGAIN; |
544 | } | 645 | } |
545 | 646 | ||
546 | mtdchar_devfs_init(); | 647 | mtd_class = class_create(THIS_MODULE, "mtd"); |
648 | |||
649 | if (IS_ERR(mtd_class)) { | ||
650 | printk(KERN_ERR "Error creating mtd class.\n"); | ||
651 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); | ||
652 | return PTR_ERR(mtd_class); | ||
653 | } | ||
654 | |||
655 | register_mtd_user(¬ifier); | ||
547 | return 0; | 656 | return 0; |
548 | } | 657 | } |
549 | 658 | ||
550 | static void __exit cleanup_mtdchar(void) | 659 | static void __exit cleanup_mtdchar(void) |
551 | { | 660 | { |
552 | mtdchar_devfs_exit(); | 661 | unregister_mtd_user(¬ifier); |
662 | class_destroy(mtd_class); | ||
553 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); | 663 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); |
554 | } | 664 | } |
555 | 665 | ||