diff options
-rw-r--r-- | drivers/mtd/mtdchar.c | 200 | ||||
-rw-r--r-- | drivers/mtd/mtdconcat.c | 51 | ||||
-rw-r--r-- | drivers/mtd/mtdpart.c | 39 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_base.c | 26 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_bbt.c | 3 | ||||
-rw-r--r-- | include/linux/mtd/mtd.h | 11 | ||||
-rw-r--r-- | include/mtd/mtd-abi.h | 27 |
7 files changed, 259 insertions, 98 deletions
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index a48210d58b92..fdc535b22e39 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c | |||
@@ -49,24 +49,18 @@ static struct mtd_notifier notifier = { | |||
49 | }; | 49 | }; |
50 | 50 | ||
51 | /* | 51 | /* |
52 | * We use file->private_data to store a pointer to the MTDdevice. | 52 | * Data structure to hold the pointer to the mtd device as well |
53 | * Since alighment is at least 32 bits, we have 2 bits free for OTP | 53 | * as mode information ofr various use cases. |
54 | * modes as well. | ||
55 | */ | 54 | */ |
56 | 55 | struct mtd_file_info { | |
57 | #define TO_MTD(file) (struct mtd_info *)((long)((file)->private_data) & ~3L) | 56 | struct mtd_info *mtd; |
58 | 57 | enum mtd_file_modes mode; | |
59 | #define MTD_MODE_OTP_FACT 1 | 58 | }; |
60 | #define MTD_MODE_OTP_USER 2 | ||
61 | #define MTD_MODE(file) ((long)((file)->private_data) & 3) | ||
62 | |||
63 | #define SET_MTD_MODE(file, mode) \ | ||
64 | do { long __p = (long)((file)->private_data); \ | ||
65 | (file)->private_data = (void *)((__p & ~3L) | mode); } while (0) | ||
66 | 59 | ||
67 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) | 60 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) |
68 | { | 61 | { |
69 | struct mtd_info *mtd = TO_MTD(file); | 62 | struct mtd_file_info *mfi = file->private_data; |
63 | struct mtd_info *mtd = mfi->mtd; | ||
70 | 64 | ||
71 | switch (orig) { | 65 | switch (orig) { |
72 | case 0: | 66 | case 0: |
@@ -97,6 +91,7 @@ static int mtd_open(struct inode *inode, struct file *file) | |||
97 | int minor = iminor(inode); | 91 | int minor = iminor(inode); |
98 | int devnum = minor >> 1; | 92 | int devnum = minor >> 1; |
99 | struct mtd_info *mtd; | 93 | struct mtd_info *mtd; |
94 | struct mtd_file_info *mfi; | ||
100 | 95 | ||
101 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); | 96 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); |
102 | 97 | ||
@@ -117,14 +112,20 @@ static int mtd_open(struct inode *inode, struct file *file) | |||
117 | return -ENODEV; | 112 | return -ENODEV; |
118 | } | 113 | } |
119 | 114 | ||
120 | file->private_data = mtd; | ||
121 | |||
122 | /* You can't open it RW if it's not a writeable device */ | 115 | /* You can't open it RW if it's not a writeable device */ |
123 | if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { | 116 | if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { |
124 | put_mtd_device(mtd); | 117 | put_mtd_device(mtd); |
125 | return -EACCES; | 118 | return -EACCES; |
126 | } | 119 | } |
127 | 120 | ||
121 | mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); | ||
122 | if (!mfi) { | ||
123 | put_mtd_device(mtd); | ||
124 | return -ENOMEM; | ||
125 | } | ||
126 | mfi->mtd = mtd; | ||
127 | file->private_data = mfi; | ||
128 | |||
128 | return 0; | 129 | return 0; |
129 | } /* mtd_open */ | 130 | } /* mtd_open */ |
130 | 131 | ||
@@ -132,16 +133,17 @@ static int mtd_open(struct inode *inode, struct file *file) | |||
132 | 133 | ||
133 | static int mtd_close(struct inode *inode, struct file *file) | 134 | static int mtd_close(struct inode *inode, struct file *file) |
134 | { | 135 | { |
135 | struct mtd_info *mtd; | 136 | struct mtd_file_info *mfi = file->private_data; |
137 | struct mtd_info *mtd = mfi->mtd; | ||
136 | 138 | ||
137 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); | 139 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); |
138 | 140 | ||
139 | mtd = TO_MTD(file); | ||
140 | |||
141 | if (mtd->sync) | 141 | if (mtd->sync) |
142 | mtd->sync(mtd); | 142 | mtd->sync(mtd); |
143 | 143 | ||
144 | put_mtd_device(mtd); | 144 | put_mtd_device(mtd); |
145 | file->private_data = NULL; | ||
146 | kfree(mfi); | ||
145 | 147 | ||
146 | return 0; | 148 | return 0; |
147 | } /* mtd_close */ | 149 | } /* mtd_close */ |
@@ -153,7 +155,8 @@ static int mtd_close(struct inode *inode, struct file *file) | |||
153 | 155 | ||
154 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) | 156 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) |
155 | { | 157 | { |
156 | struct mtd_info *mtd = TO_MTD(file); | 158 | struct mtd_file_info *mfi = file->private_data; |
159 | struct mtd_info *mtd = mfi->mtd; | ||
157 | size_t retlen=0; | 160 | size_t retlen=0; |
158 | size_t total_retlen=0; | 161 | size_t total_retlen=0; |
159 | int ret=0; | 162 | int ret=0; |
@@ -186,13 +189,26 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t | |||
186 | else | 189 | else |
187 | len = count; | 190 | len = count; |
188 | 191 | ||
189 | switch (MTD_MODE(file)) { | 192 | switch (mfi->mode) { |
190 | case MTD_MODE_OTP_FACT: | 193 | case MTD_MODE_OTP_FACTORY: |
191 | ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 194 | ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); |
192 | break; | 195 | break; |
193 | case MTD_MODE_OTP_USER: | 196 | case MTD_MODE_OTP_USER: |
194 | ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 197 | ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); |
195 | break; | 198 | break; |
199 | case MTD_MODE_RAW: | ||
200 | { | ||
201 | struct mtd_oob_ops ops; | ||
202 | |||
203 | ops.mode = MTD_OOB_RAW; | ||
204 | ops.datbuf = kbuf; | ||
205 | ops.oobbuf = NULL; | ||
206 | ops.len = len; | ||
207 | |||
208 | ret = mtd->read_oob(mtd, *ppos, &ops); | ||
209 | retlen = ops.retlen; | ||
210 | break; | ||
211 | } | ||
196 | default: | 212 | default: |
197 | ret = mtd->read(mtd, *ppos, len, &retlen, kbuf); | 213 | ret = mtd->read(mtd, *ppos, len, &retlen, kbuf); |
198 | } | 214 | } |
@@ -232,7 +248,8 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t | |||
232 | 248 | ||
233 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) | 249 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) |
234 | { | 250 | { |
235 | struct mtd_info *mtd = TO_MTD(file); | 251 | struct mtd_file_info *mfi = file->private_data; |
252 | struct mtd_info *mtd = mfi->mtd; | ||
236 | char *kbuf; | 253 | char *kbuf; |
237 | size_t retlen; | 254 | size_t retlen; |
238 | size_t total_retlen=0; | 255 | size_t total_retlen=0; |
@@ -270,8 +287,8 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count | |||
270 | return -EFAULT; | 287 | return -EFAULT; |
271 | } | 288 | } |
272 | 289 | ||
273 | switch (MTD_MODE(file)) { | 290 | switch (mfi->mode) { |
274 | case MTD_MODE_OTP_FACT: | 291 | case MTD_MODE_OTP_FACTORY: |
275 | ret = -EROFS; | 292 | ret = -EROFS; |
276 | break; | 293 | break; |
277 | case MTD_MODE_OTP_USER: | 294 | case MTD_MODE_OTP_USER: |
@@ -281,6 +298,21 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count | |||
281 | } | 298 | } |
282 | ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); | 299 | ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); |
283 | break; | 300 | break; |
301 | |||
302 | case MTD_MODE_RAW: | ||
303 | { | ||
304 | struct mtd_oob_ops ops; | ||
305 | |||
306 | ops.mode = MTD_OOB_RAW; | ||
307 | ops.datbuf = kbuf; | ||
308 | ops.oobbuf = NULL; | ||
309 | ops.len = len; | ||
310 | |||
311 | ret = mtd->write_oob(mtd, *ppos, &ops); | ||
312 | retlen = ops.retlen; | ||
313 | break; | ||
314 | } | ||
315 | |||
284 | default: | 316 | default: |
285 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); | 317 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); |
286 | } | 318 | } |
@@ -310,10 +342,41 @@ static void mtdchar_erase_callback (struct erase_info *instr) | |||
310 | wake_up((wait_queue_head_t *)instr->priv); | 342 | wake_up((wait_queue_head_t *)instr->priv); |
311 | } | 343 | } |
312 | 344 | ||
345 | #if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) | ||
346 | static int otp_select_filemode(struct mtd_file_info *mfi, int mode) | ||
347 | { | ||
348 | struct mtd_info *mtd = mfi->mtd; | ||
349 | int ret = 0; | ||
350 | |||
351 | switch (mode) { | ||
352 | case MTD_OTP_FACTORY: | ||
353 | if (!mtd->read_fact_prot_reg) | ||
354 | ret = -EOPNOTSUPP; | ||
355 | else | ||
356 | mfi->mode = MTD_MODE_OTP_FACTORY; | ||
357 | break; | ||
358 | case MTD_OTP_USER: | ||
359 | if (!mtd->read_fact_prot_reg) | ||
360 | ret = -EOPNOTSUPP; | ||
361 | else | ||
362 | mfi->mode = MTD_MODE_OTP_USER; | ||
363 | break; | ||
364 | default: | ||
365 | ret = -EINVAL; | ||
366 | case MTD_OTP_OFF: | ||
367 | break; | ||
368 | } | ||
369 | return ret; | ||
370 | } | ||
371 | #else | ||
372 | # define otp_select_filemode(f,m) -EOPNOTSUPP | ||
373 | #endif | ||
374 | |||
313 | static int mtd_ioctl(struct inode *inode, struct file *file, | 375 | static int mtd_ioctl(struct inode *inode, struct file *file, |
314 | u_int cmd, u_long arg) | 376 | u_int cmd, u_long arg) |
315 | { | 377 | { |
316 | struct mtd_info *mtd = TO_MTD(file); | 378 | struct mtd_file_info *mfi = file->private_data; |
379 | struct mtd_info *mtd = mfi->mtd; | ||
317 | void __user *argp = (void __user *)arg; | 380 | void __user *argp = (void __user *)arg; |
318 | int ret = 0; | 381 | int ret = 0; |
319 | u_long size; | 382 | u_long size; |
@@ -554,16 +617,6 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
554 | break; | 617 | break; |
555 | } | 618 | } |
556 | 619 | ||
557 | case ECCGETLAYOUT: | ||
558 | |||
559 | if (!mtd->ecclayout) | ||
560 | return -EOPNOTSUPP; | ||
561 | |||
562 | if (copy_to_user(argp, &mtd->ecclayout, | ||
563 | sizeof(struct nand_ecclayout))) | ||
564 | return -EFAULT; | ||
565 | break; | ||
566 | |||
567 | case MEMGETBADBLOCK: | 620 | case MEMGETBADBLOCK: |
568 | { | 621 | { |
569 | loff_t offs; | 622 | loff_t offs; |
@@ -596,25 +649,11 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
596 | int mode; | 649 | int mode; |
597 | if (copy_from_user(&mode, argp, sizeof(int))) | 650 | if (copy_from_user(&mode, argp, sizeof(int))) |
598 | return -EFAULT; | 651 | return -EFAULT; |
599 | SET_MTD_MODE(file, 0); | 652 | |
600 | switch (mode) { | 653 | mfi->mode = MTD_MODE_NORMAL; |
601 | case MTD_OTP_FACTORY: | 654 | |
602 | if (!mtd->read_fact_prot_reg) | 655 | ret = otp_select_filemode(mfi, mode); |
603 | ret = -EOPNOTSUPP; | 656 | |
604 | else | ||
605 | SET_MTD_MODE(file, MTD_MODE_OTP_FACT); | ||
606 | break; | ||
607 | case MTD_OTP_USER: | ||
608 | if (!mtd->read_fact_prot_reg) | ||
609 | ret = -EOPNOTSUPP; | ||
610 | else | ||
611 | SET_MTD_MODE(file, MTD_MODE_OTP_USER); | ||
612 | break; | ||
613 | default: | ||
614 | ret = -EINVAL; | ||
615 | case MTD_OTP_OFF: | ||
616 | break; | ||
617 | } | ||
618 | file->f_pos = 0; | 657 | file->f_pos = 0; |
619 | break; | 658 | break; |
620 | } | 659 | } |
@@ -626,8 +665,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
626 | if (!buf) | 665 | if (!buf) |
627 | return -ENOMEM; | 666 | return -ENOMEM; |
628 | ret = -EOPNOTSUPP; | 667 | ret = -EOPNOTSUPP; |
629 | switch (MTD_MODE(file)) { | 668 | switch (mfi->mode) { |
630 | case MTD_MODE_OTP_FACT: | 669 | case MTD_MODE_OTP_FACTORY: |
631 | if (mtd->get_fact_prot_info) | 670 | if (mtd->get_fact_prot_info) |
632 | ret = mtd->get_fact_prot_info(mtd, buf, 4096); | 671 | ret = mtd->get_fact_prot_info(mtd, buf, 4096); |
633 | break; | 672 | break; |
@@ -635,6 +674,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
635 | if (mtd->get_user_prot_info) | 674 | if (mtd->get_user_prot_info) |
636 | ret = mtd->get_user_prot_info(mtd, buf, 4096); | 675 | ret = mtd->get_user_prot_info(mtd, buf, 4096); |
637 | break; | 676 | break; |
677 | default: | ||
678 | break; | ||
638 | } | 679 | } |
639 | if (ret >= 0) { | 680 | if (ret >= 0) { |
640 | if (cmd == OTPGETREGIONCOUNT) { | 681 | if (cmd == OTPGETREGIONCOUNT) { |
@@ -653,7 +694,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
653 | { | 694 | { |
654 | struct otp_info info; | 695 | struct otp_info info; |
655 | 696 | ||
656 | if (MTD_MODE(file) != MTD_MODE_OTP_USER) | 697 | if (mfi->mode != MTD_MODE_OTP_USER) |
657 | return -EINVAL; | 698 | return -EINVAL; |
658 | if (copy_from_user(&info, argp, sizeof(info))) | 699 | if (copy_from_user(&info, argp, sizeof(info))) |
659 | return -EFAULT; | 700 | return -EFAULT; |
@@ -664,6 +705,49 @@ static int mtd_ioctl(struct inode *inode, struct file *file, | |||
664 | } | 705 | } |
665 | #endif | 706 | #endif |
666 | 707 | ||
708 | case ECCGETLAYOUT: | ||
709 | { | ||
710 | if (!mtd->ecclayout) | ||
711 | return -EOPNOTSUPP; | ||
712 | |||
713 | if (copy_to_user(argp, &mtd->ecclayout, | ||
714 | sizeof(struct nand_ecclayout))) | ||
715 | return -EFAULT; | ||
716 | break; | ||
717 | } | ||
718 | |||
719 | case ECCGETSTATS: | ||
720 | { | ||
721 | if (copy_to_user(argp, &mtd->ecc_stats, | ||
722 | sizeof(struct mtd_ecc_stats))) | ||
723 | return -EFAULT; | ||
724 | break; | ||
725 | } | ||
726 | |||
727 | case MTDFILEMODE: | ||
728 | { | ||
729 | mfi->mode = 0; | ||
730 | |||
731 | switch(arg) { | ||
732 | case MTD_MODE_OTP_FACTORY: | ||
733 | case MTD_MODE_OTP_USER: | ||
734 | ret = otp_select_filemode(mfi, arg); | ||
735 | break; | ||
736 | |||
737 | case MTD_MODE_RAW: | ||
738 | if (!mtd->read_oob || !mtd->write_oob) | ||
739 | return -EOPNOTSUPP; | ||
740 | mfi->mode = arg; | ||
741 | |||
742 | case MTD_MODE_NORMAL: | ||
743 | break; | ||
744 | default: | ||
745 | ret = -EINVAL; | ||
746 | } | ||
747 | file->f_pos = 0; | ||
748 | break; | ||
749 | } | ||
750 | |||
667 | default: | 751 | default: |
668 | ret = -ENOTTY; | 752 | ret = -ENOTTY; |
669 | } | 753 | } |
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 3c8d5e6fa010..1fea631b5852 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c | |||
@@ -56,7 +56,7 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, | |||
56 | size_t * retlen, u_char * buf) | 56 | size_t * retlen, u_char * buf) |
57 | { | 57 | { |
58 | struct mtd_concat *concat = CONCAT(mtd); | 58 | struct mtd_concat *concat = CONCAT(mtd); |
59 | int ret = 0, err = -EINVAL; | 59 | int ret = 0, err; |
60 | int i; | 60 | int i; |
61 | 61 | ||
62 | *retlen = 0; | 62 | *retlen = 0; |
@@ -80,28 +80,29 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, | |||
80 | 80 | ||
81 | err = subdev->read(subdev, from, size, &retsize, buf); | 81 | err = subdev->read(subdev, from, size, &retsize, buf); |
82 | 82 | ||
83 | if (err && (err != -EBADMSG) && (err != -EUCLEAN)) | ||
84 | break; | ||
85 | |||
86 | /* Save information about bitflips! */ | 83 | /* Save information about bitflips! */ |
87 | if (err) { | 84 | if (unlikely(err)) { |
88 | if (err == -EBADMSG) | 85 | if (err == -EBADMSG) { |
89 | ret = err; | 86 | mtd->ecc_stats.failed++; |
90 | else if (!ret) | ||
91 | ret = err; | 87 | ret = err; |
92 | err = 0; | 88 | } else if (err == -EUCLEAN) { |
89 | mtd->ecc_stats.corrected++; | ||
90 | /* Do not overwrite -EBADMSG !! */ | ||
91 | if (!ret) | ||
92 | ret = err; | ||
93 | } else | ||
94 | return err; | ||
93 | } | 95 | } |
94 | 96 | ||
95 | *retlen += retsize; | 97 | *retlen += retsize; |
96 | len -= size; | 98 | len -= size; |
97 | if (len == 0) | 99 | if (len == 0) |
98 | break; | 100 | return ret; |
99 | 101 | ||
100 | err = -EINVAL; | ||
101 | buf += size; | 102 | buf += size; |
102 | from = 0; | 103 | from = 0; |
103 | } | 104 | } |
104 | return err ? err : ret; | 105 | return -EINVAL; |
105 | } | 106 | } |
106 | 107 | ||
107 | static int | 108 | static int |
@@ -244,7 +245,7 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) | |||
244 | { | 245 | { |
245 | struct mtd_concat *concat = CONCAT(mtd); | 246 | struct mtd_concat *concat = CONCAT(mtd); |
246 | struct mtd_oob_ops devops = *ops; | 247 | struct mtd_oob_ops devops = *ops; |
247 | int i, err; | 248 | int i, err, ret = 0; |
248 | 249 | ||
249 | ops->retlen = 0; | 250 | ops->retlen = 0; |
250 | 251 | ||
@@ -262,12 +263,24 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) | |||
262 | 263 | ||
263 | err = subdev->read_oob(subdev, from, &devops); | 264 | err = subdev->read_oob(subdev, from, &devops); |
264 | ops->retlen += devops.retlen; | 265 | ops->retlen += devops.retlen; |
265 | if (err) | 266 | |
266 | return err; | 267 | /* Save information about bitflips! */ |
268 | if (unlikely(err)) { | ||
269 | if (err == -EBADMSG) { | ||
270 | mtd->ecc_stats.failed++; | ||
271 | ret = err; | ||
272 | } else if (err == -EUCLEAN) { | ||
273 | mtd->ecc_stats.corrected++; | ||
274 | /* Do not overwrite -EBADMSG !! */ | ||
275 | if (!ret) | ||
276 | ret = err; | ||
277 | } else | ||
278 | return err; | ||
279 | } | ||
267 | 280 | ||
268 | devops.len = ops->len - ops->retlen; | 281 | devops.len = ops->len - ops->retlen; |
269 | if (!devops.len) | 282 | if (!devops.len) |
270 | return 0; | 283 | return ret; |
271 | 284 | ||
272 | if (devops.datbuf) | 285 | if (devops.datbuf) |
273 | devops.datbuf += devops.retlen; | 286 | devops.datbuf += devops.retlen; |
@@ -655,6 +668,8 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) | |||
655 | } | 668 | } |
656 | 669 | ||
657 | err = subdev->block_markbad(subdev, ofs); | 670 | err = subdev->block_markbad(subdev, ofs); |
671 | if (!err) | ||
672 | mtd->ecc_stats.badblocks++; | ||
658 | break; | 673 | break; |
659 | } | 674 | } |
660 | 675 | ||
@@ -717,6 +732,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c | |||
717 | if (subdev[0]->block_markbad) | 732 | if (subdev[0]->block_markbad) |
718 | concat->mtd.block_markbad = concat_block_markbad; | 733 | concat->mtd.block_markbad = concat_block_markbad; |
719 | 734 | ||
735 | concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; | ||
736 | |||
720 | concat->subdev[0] = subdev[0]; | 737 | concat->subdev[0] = subdev[0]; |
721 | 738 | ||
722 | for (i = 1; i < num_devs; i++) { | 739 | for (i = 1; i < num_devs; i++) { |
@@ -744,6 +761,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c | |||
744 | subdev[i]->flags & MTD_WRITEABLE; | 761 | subdev[i]->flags & MTD_WRITEABLE; |
745 | } | 762 | } |
746 | concat->mtd.size += subdev[i]->size; | 763 | concat->mtd.size += subdev[i]->size; |
764 | concat->mtd.ecc_stats.badblocks += | ||
765 | subdev[i]->ecc_stats.badblocks; | ||
747 | if (concat->mtd.writesize != subdev[i]->writesize || | 766 | if (concat->mtd.writesize != subdev[i]->writesize || |
748 | concat->mtd.oobsize != subdev[i]->oobsize || | 767 | concat->mtd.oobsize != subdev[i]->oobsize || |
749 | concat->mtd.ecctype != subdev[i]->ecctype || | 768 | concat->mtd.ecctype != subdev[i]->ecctype || |
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index f22aeccf01e7..77a7123a5c56 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c | |||
@@ -51,12 +51,21 @@ static int part_read (struct mtd_info *mtd, loff_t from, size_t len, | |||
51 | size_t *retlen, u_char *buf) | 51 | size_t *retlen, u_char *buf) |
52 | { | 52 | { |
53 | struct mtd_part *part = PART(mtd); | 53 | struct mtd_part *part = PART(mtd); |
54 | int res; | ||
55 | |||
54 | if (from >= mtd->size) | 56 | if (from >= mtd->size) |
55 | len = 0; | 57 | len = 0; |
56 | else if (from + len > mtd->size) | 58 | else if (from + len > mtd->size) |
57 | len = mtd->size - from; | 59 | len = mtd->size - from; |
58 | return part->master->read (part->master, from + part->offset, | 60 | res = part->master->read (part->master, from + part->offset, |
59 | len, retlen, buf); | 61 | len, retlen, buf); |
62 | if (unlikely(res)) { | ||
63 | if (res == -EUCLEAN) | ||
64 | mtd->ecc_stats.corrected++; | ||
65 | if (res == -EBADMSG) | ||
66 | mtd->ecc_stats.failed++; | ||
67 | } | ||
68 | return res; | ||
60 | } | 69 | } |
61 | 70 | ||
62 | static int part_point (struct mtd_info *mtd, loff_t from, size_t len, | 71 | static int part_point (struct mtd_info *mtd, loff_t from, size_t len, |
@@ -82,12 +91,21 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from, | |||
82 | struct mtd_oob_ops *ops) | 91 | struct mtd_oob_ops *ops) |
83 | { | 92 | { |
84 | struct mtd_part *part = PART(mtd); | 93 | struct mtd_part *part = PART(mtd); |
94 | int res; | ||
85 | 95 | ||
86 | if (from >= mtd->size) | 96 | if (from >= mtd->size) |
87 | return -EINVAL; | 97 | return -EINVAL; |
88 | if (from + ops->len > mtd->size) | 98 | if (from + ops->len > mtd->size) |
89 | return -EINVAL; | 99 | return -EINVAL; |
90 | return part->master->read_oob(part->master, from + part->offset, ops); | 100 | res = part->master->read_oob(part->master, from + part->offset, ops); |
101 | |||
102 | if (unlikely(res)) { | ||
103 | if (res == -EUCLEAN) | ||
104 | mtd->ecc_stats.corrected++; | ||
105 | if (res == -EBADMSG) | ||
106 | mtd->ecc_stats.failed++; | ||
107 | } | ||
108 | return res; | ||
91 | } | 109 | } |
92 | 110 | ||
93 | static int part_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, | 111 | static int part_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, |
@@ -246,12 +264,17 @@ static int part_block_isbad (struct mtd_info *mtd, loff_t ofs) | |||
246 | static int part_block_markbad (struct mtd_info *mtd, loff_t ofs) | 264 | static int part_block_markbad (struct mtd_info *mtd, loff_t ofs) |
247 | { | 265 | { |
248 | struct mtd_part *part = PART(mtd); | 266 | struct mtd_part *part = PART(mtd); |
267 | int res; | ||
268 | |||
249 | if (!(mtd->flags & MTD_WRITEABLE)) | 269 | if (!(mtd->flags & MTD_WRITEABLE)) |
250 | return -EROFS; | 270 | return -EROFS; |
251 | if (ofs >= mtd->size) | 271 | if (ofs >= mtd->size) |
252 | return -EINVAL; | 272 | return -EINVAL; |
253 | ofs += part->offset; | 273 | ofs += part->offset; |
254 | return part->master->block_markbad(part->master, ofs); | 274 | res = part->master->block_markbad(part->master, ofs); |
275 | if (!res) | ||
276 | mtd->ecc_stats.badblocks++; | ||
277 | return res; | ||
255 | } | 278 | } |
256 | 279 | ||
257 | /* | 280 | /* |
@@ -436,6 +459,16 @@ int add_mtd_partitions(struct mtd_info *master, | |||
436 | } | 459 | } |
437 | 460 | ||
438 | slave->mtd.ecclayout = master->ecclayout; | 461 | slave->mtd.ecclayout = master->ecclayout; |
462 | if (master->block_isbad) { | ||
463 | uint32_t offs = 0; | ||
464 | |||
465 | while(offs < slave->mtd.size) { | ||
466 | if (master->block_isbad(master, | ||
467 | offs + slave->offset)) | ||
468 | slave->mtd.ecc_stats.badblocks++; | ||
469 | offs += slave->mtd.erasesize; | ||
470 | } | ||
471 | } | ||
439 | 472 | ||
440 | if(parts[i].mtdp) | 473 | if(parts[i].mtdp) |
441 | { /* store the object pointer (caller may or may not register it */ | 474 | { /* store the object pointer (caller may or may not register it */ |
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 7a3a44907715..ea6d2c334aed 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c | |||
@@ -347,7 +347,7 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) | |||
347 | { | 347 | { |
348 | struct nand_chip *chip = mtd->priv; | 348 | struct nand_chip *chip = mtd->priv; |
349 | uint8_t buf[2] = { 0, 0 }; | 349 | uint8_t buf[2] = { 0, 0 }; |
350 | int block; | 350 | int block, ret; |
351 | 351 | ||
352 | /* Get block number */ | 352 | /* Get block number */ |
353 | block = ((int)ofs) >> chip->bbt_erase_shift; | 353 | block = ((int)ofs) >> chip->bbt_erase_shift; |
@@ -356,16 +356,22 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) | |||
356 | 356 | ||
357 | /* Do we have a flash based bad block table ? */ | 357 | /* Do we have a flash based bad block table ? */ |
358 | if (chip->options & NAND_USE_FLASH_BBT) | 358 | if (chip->options & NAND_USE_FLASH_BBT) |
359 | return nand_update_bbt(mtd, ofs); | 359 | ret = nand_update_bbt(mtd, ofs); |
360 | 360 | else { | |
361 | /* We write two bytes, so we dont have to mess with 16 bit access */ | 361 | /* We write two bytes, so we dont have to mess with 16 bit |
362 | ofs += mtd->oobsize; | 362 | * access |
363 | chip->ops.len = 2; | 363 | */ |
364 | chip->ops.datbuf = NULL; | 364 | ofs += mtd->oobsize; |
365 | chip->ops.oobbuf = buf; | 365 | chip->ops.len = 2; |
366 | chip->ops.ooboffs = chip->badblockpos & ~0x01; | 366 | chip->ops.datbuf = NULL; |
367 | chip->ops.oobbuf = buf; | ||
368 | chip->ops.ooboffs = chip->badblockpos & ~0x01; | ||
367 | 369 | ||
368 | return nand_do_write_oob(mtd, ofs, &chip->ops); | 370 | ret = nand_do_write_oob(mtd, ofs, &chip->ops); |
371 | } | ||
372 | if (!ret) | ||
373 | mtd->ecc_stats.badblocks++; | ||
374 | return ret; | ||
369 | } | 375 | } |
370 | 376 | ||
371 | /** | 377 | /** |
diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 480c3cbf9bf9..a612c4ea8194 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c | |||
@@ -176,6 +176,7 @@ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num, | |||
176 | printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n", | 176 | printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n", |
177 | ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); | 177 | ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); |
178 | this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06); | 178 | this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06); |
179 | mtd->ecc_stats.bbtblocks++; | ||
179 | continue; | 180 | continue; |
180 | } | 181 | } |
181 | /* Leave it for now, if its matured we can move this | 182 | /* Leave it for now, if its matured we can move this |
@@ -187,6 +188,7 @@ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num, | |||
187 | this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06); | 188 | this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06); |
188 | else | 189 | else |
189 | this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06); | 190 | this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06); |
191 | mtd->ecc_stats.badblocks++; | ||
190 | } | 192 | } |
191 | } | 193 | } |
192 | totlen -= len; | 194 | totlen -= len; |
@@ -431,6 +433,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, | |||
431 | this->bbt[i >> 3] |= 0x03 << (i & 0x6); | 433 | this->bbt[i >> 3] |= 0x03 << (i & 0x6); |
432 | printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", | 434 | printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", |
433 | i >> 1, (unsigned int)from); | 435 | i >> 1, (unsigned int)from); |
436 | mtd->ecc_stats.badblocks++; | ||
434 | } | 437 | } |
435 | 438 | ||
436 | i += 2; | 439 | i += 2; |
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index e75bb584e80b..9536567d041b 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h | |||
@@ -56,17 +56,6 @@ struct mtd_erase_region_info { | |||
56 | u_int32_t numblocks; /* Number of blocks of erasesize in this region */ | 56 | u_int32_t numblocks; /* Number of blocks of erasesize in this region */ |
57 | }; | 57 | }; |
58 | 58 | ||
59 | /** | ||
60 | * struct mtd_ecc_stats - error correction status | ||
61 | * | ||
62 | * @corrected: number of corrected bits | ||
63 | * @failed: number of uncorrectable errors | ||
64 | */ | ||
65 | struct mtd_ecc_stats { | ||
66 | unsigned long corrected; | ||
67 | unsigned long failed; | ||
68 | }; | ||
69 | |||
70 | /* | 59 | /* |
71 | * oob operation modes | 60 | * oob operation modes |
72 | * | 61 | * |
diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h index 54c673f9648d..c11a589bdedf 100644 --- a/include/mtd/mtd-abi.h +++ b/include/mtd/mtd-abi.h | |||
@@ -99,6 +99,8 @@ struct otp_info { | |||
99 | #define OTPGETREGIONINFO _IOW('M', 15, struct otp_info) | 99 | #define OTPGETREGIONINFO _IOW('M', 15, struct otp_info) |
100 | #define OTPLOCK _IOR('M', 16, struct otp_info) | 100 | #define OTPLOCK _IOR('M', 16, struct otp_info) |
101 | #define ECCGETLAYOUT _IOR('M', 17, struct nand_ecclayout) | 101 | #define ECCGETLAYOUT _IOR('M', 17, struct nand_ecclayout) |
102 | #define ECCGETSTATS _IOR('M', 18, struct mtd_ecc_stats) | ||
103 | #define MTDFILEMODE _IO('M', 19) | ||
102 | 104 | ||
103 | /* | 105 | /* |
104 | * Obsolete legacy interface. Keep it in order not to break userspace | 106 | * Obsolete legacy interface. Keep it in order not to break userspace |
@@ -128,4 +130,29 @@ struct nand_ecclayout { | |||
128 | struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; | 130 | struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; |
129 | }; | 131 | }; |
130 | 132 | ||
133 | /** | ||
134 | * struct mtd_ecc_stats - error correction status | ||
135 | * | ||
136 | * @corrected: number of corrected bits | ||
137 | * @failed: number of uncorrectable errors | ||
138 | * @badblocks: number of bad blocks in this partition | ||
139 | * @bbtblocks: number of blocks reserved for bad block tables | ||
140 | */ | ||
141 | struct mtd_ecc_stats { | ||
142 | uint32_t corrected; | ||
143 | uint32_t failed; | ||
144 | uint32_t badblocks; | ||
145 | uint32_t bbtblocks; | ||
146 | }; | ||
147 | |||
148 | /* | ||
149 | * Read/write file modes for access to MTD | ||
150 | */ | ||
151 | enum mtd_file_modes { | ||
152 | MTD_MODE_NORMAL = MTD_OTP_OFF, | ||
153 | MTD_MODE_OTP_FACTORY = MTD_OTP_FACTORY, | ||
154 | MTD_MODE_OTP_USER = MTD_OTP_USER, | ||
155 | MTD_MODE_RAW, | ||
156 | }; | ||
157 | |||
131 | #endif /* __MTD_ABI_H__ */ | 158 | #endif /* __MTD_ABI_H__ */ |