diff options
Diffstat (limited to 'drivers/mtd/mtdconcat.c')
-rw-r--r-- | drivers/mtd/mtdconcat.c | 335 |
1 files changed, 188 insertions, 147 deletions
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 9af840364a74..1fea631b5852 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c | |||
@@ -19,6 +19,8 @@ | |||
19 | #include <linux/mtd/mtd.h> | 19 | #include <linux/mtd/mtd.h> |
20 | #include <linux/mtd/concat.h> | 20 | #include <linux/mtd/concat.h> |
21 | 21 | ||
22 | #include <asm/div64.h> | ||
23 | |||
22 | /* | 24 | /* |
23 | * Our storage structure: | 25 | * Our storage structure: |
24 | * Subdev points to an array of pointers to struct mtd_info objects | 26 | * Subdev points to an array of pointers to struct mtd_info objects |
@@ -54,7 +56,7 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, | |||
54 | size_t * retlen, u_char * buf) | 56 | size_t * retlen, u_char * buf) |
55 | { | 57 | { |
56 | struct mtd_concat *concat = CONCAT(mtd); | 58 | struct mtd_concat *concat = CONCAT(mtd); |
57 | int err = -EINVAL; | 59 | int ret = 0, err; |
58 | int i; | 60 | int i; |
59 | 61 | ||
60 | *retlen = 0; | 62 | *retlen = 0; |
@@ -78,19 +80,29 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, | |||
78 | 80 | ||
79 | err = subdev->read(subdev, from, size, &retsize, buf); | 81 | err = subdev->read(subdev, from, size, &retsize, buf); |
80 | 82 | ||
81 | if (err) | 83 | /* Save information about bitflips! */ |
82 | break; | 84 | if (unlikely(err)) { |
85 | if (err == -EBADMSG) { | ||
86 | mtd->ecc_stats.failed++; | ||
87 | ret = err; | ||
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; | ||
95 | } | ||
83 | 96 | ||
84 | *retlen += retsize; | 97 | *retlen += retsize; |
85 | len -= size; | 98 | len -= size; |
86 | if (len == 0) | 99 | if (len == 0) |
87 | break; | 100 | return ret; |
88 | 101 | ||
89 | err = -EINVAL; | ||
90 | buf += size; | 102 | buf += size; |
91 | from = 0; | 103 | from = 0; |
92 | } | 104 | } |
93 | return err; | 105 | return -EINVAL; |
94 | } | 106 | } |
95 | 107 | ||
96 | static int | 108 | static int |
@@ -141,211 +153,185 @@ concat_write(struct mtd_info *mtd, loff_t to, size_t len, | |||
141 | } | 153 | } |
142 | 154 | ||
143 | static int | 155 | static int |
144 | concat_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, | 156 | concat_writev(struct mtd_info *mtd, const struct kvec *vecs, |
145 | size_t * retlen, u_char * buf, u_char * eccbuf, | 157 | unsigned long count, loff_t to, size_t * retlen) |
146 | struct nand_oobinfo *oobsel) | ||
147 | { | 158 | { |
148 | struct mtd_concat *concat = CONCAT(mtd); | 159 | struct mtd_concat *concat = CONCAT(mtd); |
149 | int err = -EINVAL; | 160 | struct kvec *vecs_copy; |
161 | unsigned long entry_low, entry_high; | ||
162 | size_t total_len = 0; | ||
150 | int i; | 163 | int i; |
164 | int err = -EINVAL; | ||
151 | 165 | ||
152 | *retlen = 0; | 166 | if (!(mtd->flags & MTD_WRITEABLE)) |
153 | 167 | return -EROFS; | |
154 | for (i = 0; i < concat->num_subdev; i++) { | ||
155 | struct mtd_info *subdev = concat->subdev[i]; | ||
156 | size_t size, retsize; | ||
157 | |||
158 | if (from >= subdev->size) { | ||
159 | /* Not destined for this subdev */ | ||
160 | size = 0; | ||
161 | from -= subdev->size; | ||
162 | continue; | ||
163 | } | ||
164 | |||
165 | if (from + len > subdev->size) | ||
166 | /* First part goes into this subdev */ | ||
167 | size = subdev->size - from; | ||
168 | else | ||
169 | /* Entire transaction goes into this subdev */ | ||
170 | size = len; | ||
171 | 168 | ||
172 | if (subdev->read_ecc) | 169 | *retlen = 0; |
173 | err = subdev->read_ecc(subdev, from, size, | ||
174 | &retsize, buf, eccbuf, oobsel); | ||
175 | else | ||
176 | err = -EINVAL; | ||
177 | 170 | ||
178 | if (err) | 171 | /* Calculate total length of data */ |
179 | break; | 172 | for (i = 0; i < count; i++) |
173 | total_len += vecs[i].iov_len; | ||
180 | 174 | ||
181 | *retlen += retsize; | 175 | /* Do not allow write past end of device */ |
182 | len -= size; | 176 | if ((to + total_len) > mtd->size) |
183 | if (len == 0) | 177 | return -EINVAL; |
184 | break; | ||
185 | 178 | ||
186 | err = -EINVAL; | 179 | /* Check alignment */ |
187 | buf += size; | 180 | if (mtd->writesize > 1) { |
188 | if (eccbuf) { | 181 | loff_t __to = to; |
189 | eccbuf += subdev->oobsize; | 182 | if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize)) |
190 | /* in nand.c at least, eccbufs are | 183 | return -EINVAL; |
191 | tagged with 2 (int)eccstatus'; we | ||
192 | must account for these */ | ||
193 | eccbuf += 2 * (sizeof (int)); | ||
194 | } | ||
195 | from = 0; | ||
196 | } | 184 | } |
197 | return err; | ||
198 | } | ||
199 | 185 | ||
200 | static int | 186 | /* make a copy of vecs */ |
201 | concat_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, | 187 | vecs_copy = kmalloc(sizeof(struct kvec) * count, GFP_KERNEL); |
202 | size_t * retlen, const u_char * buf, u_char * eccbuf, | 188 | if (!vecs_copy) |
203 | struct nand_oobinfo *oobsel) | 189 | return -ENOMEM; |
204 | { | 190 | memcpy(vecs_copy, vecs, sizeof(struct kvec) * count); |
205 | struct mtd_concat *concat = CONCAT(mtd); | ||
206 | int err = -EINVAL; | ||
207 | int i; | ||
208 | |||
209 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
210 | return -EROFS; | ||
211 | |||
212 | *retlen = 0; | ||
213 | 191 | ||
192 | entry_low = 0; | ||
214 | for (i = 0; i < concat->num_subdev; i++) { | 193 | for (i = 0; i < concat->num_subdev; i++) { |
215 | struct mtd_info *subdev = concat->subdev[i]; | 194 | struct mtd_info *subdev = concat->subdev[i]; |
216 | size_t size, retsize; | 195 | size_t size, wsize, retsize, old_iov_len; |
217 | 196 | ||
218 | if (to >= subdev->size) { | 197 | if (to >= subdev->size) { |
219 | size = 0; | ||
220 | to -= subdev->size; | 198 | to -= subdev->size; |
221 | continue; | 199 | continue; |
222 | } | 200 | } |
223 | if (to + len > subdev->size) | 201 | |
224 | size = subdev->size - to; | 202 | size = min(total_len, (size_t)(subdev->size - to)); |
225 | else | 203 | wsize = size; /* store for future use */ |
226 | size = len; | 204 | |
205 | entry_high = entry_low; | ||
206 | while (entry_high < count) { | ||
207 | if (size <= vecs_copy[entry_high].iov_len) | ||
208 | break; | ||
209 | size -= vecs_copy[entry_high++].iov_len; | ||
210 | } | ||
211 | |||
212 | old_iov_len = vecs_copy[entry_high].iov_len; | ||
213 | vecs_copy[entry_high].iov_len = size; | ||
227 | 214 | ||
228 | if (!(subdev->flags & MTD_WRITEABLE)) | 215 | if (!(subdev->flags & MTD_WRITEABLE)) |
229 | err = -EROFS; | 216 | err = -EROFS; |
230 | else if (subdev->write_ecc) | ||
231 | err = subdev->write_ecc(subdev, to, size, | ||
232 | &retsize, buf, eccbuf, oobsel); | ||
233 | else | 217 | else |
234 | err = -EINVAL; | 218 | err = subdev->writev(subdev, &vecs_copy[entry_low], |
219 | entry_high - entry_low + 1, to, &retsize); | ||
220 | |||
221 | vecs_copy[entry_high].iov_len = old_iov_len - size; | ||
222 | vecs_copy[entry_high].iov_base += size; | ||
223 | |||
224 | entry_low = entry_high; | ||
235 | 225 | ||
236 | if (err) | 226 | if (err) |
237 | break; | 227 | break; |
238 | 228 | ||
239 | *retlen += retsize; | 229 | *retlen += retsize; |
240 | len -= size; | 230 | total_len -= wsize; |
241 | if (len == 0) | 231 | |
232 | if (total_len == 0) | ||
242 | break; | 233 | break; |
243 | 234 | ||
244 | err = -EINVAL; | 235 | err = -EINVAL; |
245 | buf += size; | ||
246 | if (eccbuf) | ||
247 | eccbuf += subdev->oobsize; | ||
248 | to = 0; | 236 | to = 0; |
249 | } | 237 | } |
238 | |||
239 | kfree(vecs_copy); | ||
250 | return err; | 240 | return err; |
251 | } | 241 | } |
252 | 242 | ||
253 | static int | 243 | static int |
254 | concat_read_oob(struct mtd_info *mtd, loff_t from, size_t len, | 244 | concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) |
255 | size_t * retlen, u_char * buf) | ||
256 | { | 245 | { |
257 | struct mtd_concat *concat = CONCAT(mtd); | 246 | struct mtd_concat *concat = CONCAT(mtd); |
258 | int err = -EINVAL; | 247 | struct mtd_oob_ops devops = *ops; |
259 | int i; | 248 | int i, err, ret = 0; |
260 | 249 | ||
261 | *retlen = 0; | 250 | ops->retlen = 0; |
262 | 251 | ||
263 | for (i = 0; i < concat->num_subdev; i++) { | 252 | for (i = 0; i < concat->num_subdev; i++) { |
264 | struct mtd_info *subdev = concat->subdev[i]; | 253 | struct mtd_info *subdev = concat->subdev[i]; |
265 | size_t size, retsize; | ||
266 | 254 | ||
267 | if (from >= subdev->size) { | 255 | if (from >= subdev->size) { |
268 | /* Not destined for this subdev */ | ||
269 | size = 0; | ||
270 | from -= subdev->size; | 256 | from -= subdev->size; |
271 | continue; | 257 | continue; |
272 | } | 258 | } |
273 | if (from + len > subdev->size) | ||
274 | /* First part goes into this subdev */ | ||
275 | size = subdev->size - from; | ||
276 | else | ||
277 | /* Entire transaction goes into this subdev */ | ||
278 | size = len; | ||
279 | 259 | ||
280 | if (subdev->read_oob) | 260 | /* partial read ? */ |
281 | err = subdev->read_oob(subdev, from, size, | 261 | if (from + devops.len > subdev->size) |
282 | &retsize, buf); | 262 | devops.len = subdev->size - from; |
283 | else | 263 | |
284 | err = -EINVAL; | 264 | err = subdev->read_oob(subdev, from, &devops); |
265 | ops->retlen += devops.retlen; | ||
266 | |||
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 | } | ||
285 | 280 | ||
286 | if (err) | 281 | devops.len = ops->len - ops->retlen; |
287 | break; | 282 | if (!devops.len) |
283 | return ret; | ||
288 | 284 | ||
289 | *retlen += retsize; | 285 | if (devops.datbuf) |
290 | len -= size; | 286 | devops.datbuf += devops.retlen; |
291 | if (len == 0) | 287 | if (devops.oobbuf) |
292 | break; | 288 | devops.oobbuf += devops.ooblen; |
293 | 289 | ||
294 | err = -EINVAL; | ||
295 | buf += size; | ||
296 | from = 0; | 290 | from = 0; |
297 | } | 291 | } |
298 | return err; | 292 | return -EINVAL; |
299 | } | 293 | } |
300 | 294 | ||
301 | static int | 295 | static int |
302 | concat_write_oob(struct mtd_info *mtd, loff_t to, size_t len, | 296 | concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) |
303 | size_t * retlen, const u_char * buf) | ||
304 | { | 297 | { |
305 | struct mtd_concat *concat = CONCAT(mtd); | 298 | struct mtd_concat *concat = CONCAT(mtd); |
306 | int err = -EINVAL; | 299 | struct mtd_oob_ops devops = *ops; |
307 | int i; | 300 | int i, err; |
308 | 301 | ||
309 | if (!(mtd->flags & MTD_WRITEABLE)) | 302 | if (!(mtd->flags & MTD_WRITEABLE)) |
310 | return -EROFS; | 303 | return -EROFS; |
311 | 304 | ||
312 | *retlen = 0; | 305 | ops->retlen = 0; |
313 | 306 | ||
314 | for (i = 0; i < concat->num_subdev; i++) { | 307 | for (i = 0; i < concat->num_subdev; i++) { |
315 | struct mtd_info *subdev = concat->subdev[i]; | 308 | struct mtd_info *subdev = concat->subdev[i]; |
316 | size_t size, retsize; | ||
317 | 309 | ||
318 | if (to >= subdev->size) { | 310 | if (to >= subdev->size) { |
319 | size = 0; | ||
320 | to -= subdev->size; | 311 | to -= subdev->size; |
321 | continue; | 312 | continue; |
322 | } | 313 | } |
323 | if (to + len > subdev->size) | ||
324 | size = subdev->size - to; | ||
325 | else | ||
326 | size = len; | ||
327 | 314 | ||
328 | if (!(subdev->flags & MTD_WRITEABLE)) | 315 | /* partial write ? */ |
329 | err = -EROFS; | 316 | if (to + devops.len > subdev->size) |
330 | else if (subdev->write_oob) | 317 | devops.len = subdev->size - to; |
331 | err = subdev->write_oob(subdev, to, size, &retsize, | ||
332 | buf); | ||
333 | else | ||
334 | err = -EINVAL; | ||
335 | 318 | ||
319 | err = subdev->write_oob(subdev, to, &devops); | ||
320 | ops->retlen += devops.retlen; | ||
336 | if (err) | 321 | if (err) |
337 | break; | 322 | return err; |
338 | 323 | ||
339 | *retlen += retsize; | 324 | devops.len = ops->len - ops->retlen; |
340 | len -= size; | 325 | if (!devops.len) |
341 | if (len == 0) | 326 | return 0; |
342 | break; | ||
343 | 327 | ||
344 | err = -EINVAL; | 328 | if (devops.datbuf) |
345 | buf += size; | 329 | devops.datbuf += devops.retlen; |
330 | if (devops.oobbuf) | ||
331 | devops.oobbuf += devops.ooblen; | ||
346 | to = 0; | 332 | to = 0; |
347 | } | 333 | } |
348 | return err; | 334 | return -EINVAL; |
349 | } | 335 | } |
350 | 336 | ||
351 | static void concat_erase_callback(struct erase_info *instr) | 337 | static void concat_erase_callback(struct erase_info *instr) |
@@ -636,6 +622,60 @@ static void concat_resume(struct mtd_info *mtd) | |||
636 | } | 622 | } |
637 | } | 623 | } |
638 | 624 | ||
625 | static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) | ||
626 | { | ||
627 | struct mtd_concat *concat = CONCAT(mtd); | ||
628 | int i, res = 0; | ||
629 | |||
630 | if (!concat->subdev[0]->block_isbad) | ||
631 | return res; | ||
632 | |||
633 | if (ofs > mtd->size) | ||
634 | return -EINVAL; | ||
635 | |||
636 | for (i = 0; i < concat->num_subdev; i++) { | ||
637 | struct mtd_info *subdev = concat->subdev[i]; | ||
638 | |||
639 | if (ofs >= subdev->size) { | ||
640 | ofs -= subdev->size; | ||
641 | continue; | ||
642 | } | ||
643 | |||
644 | res = subdev->block_isbad(subdev, ofs); | ||
645 | break; | ||
646 | } | ||
647 | |||
648 | return res; | ||
649 | } | ||
650 | |||
651 | static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) | ||
652 | { | ||
653 | struct mtd_concat *concat = CONCAT(mtd); | ||
654 | int i, err = -EINVAL; | ||
655 | |||
656 | if (!concat->subdev[0]->block_markbad) | ||
657 | return 0; | ||
658 | |||
659 | if (ofs > mtd->size) | ||
660 | return -EINVAL; | ||
661 | |||
662 | for (i = 0; i < concat->num_subdev; i++) { | ||
663 | struct mtd_info *subdev = concat->subdev[i]; | ||
664 | |||
665 | if (ofs >= subdev->size) { | ||
666 | ofs -= subdev->size; | ||
667 | continue; | ||
668 | } | ||
669 | |||
670 | err = subdev->block_markbad(subdev, ofs); | ||
671 | if (!err) | ||
672 | mtd->ecc_stats.badblocks++; | ||
673 | break; | ||
674 | } | ||
675 | |||
676 | return err; | ||
677 | } | ||
678 | |||
639 | /* | 679 | /* |
640 | * This function constructs a virtual MTD device by concatenating | 680 | * This function constructs a virtual MTD device by concatenating |
641 | * num_devs MTD devices. A pointer to the new device object is | 681 | * num_devs MTD devices. A pointer to the new device object is |
@@ -677,18 +717,22 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c | |||
677 | concat->mtd.flags = subdev[0]->flags; | 717 | concat->mtd.flags = subdev[0]->flags; |
678 | concat->mtd.size = subdev[0]->size; | 718 | concat->mtd.size = subdev[0]->size; |
679 | concat->mtd.erasesize = subdev[0]->erasesize; | 719 | concat->mtd.erasesize = subdev[0]->erasesize; |
680 | concat->mtd.oobblock = subdev[0]->oobblock; | 720 | concat->mtd.writesize = subdev[0]->writesize; |
681 | concat->mtd.oobsize = subdev[0]->oobsize; | 721 | concat->mtd.oobsize = subdev[0]->oobsize; |
682 | concat->mtd.ecctype = subdev[0]->ecctype; | 722 | concat->mtd.ecctype = subdev[0]->ecctype; |
683 | concat->mtd.eccsize = subdev[0]->eccsize; | 723 | concat->mtd.eccsize = subdev[0]->eccsize; |
684 | if (subdev[0]->read_ecc) | 724 | if (subdev[0]->writev) |
685 | concat->mtd.read_ecc = concat_read_ecc; | 725 | concat->mtd.writev = concat_writev; |
686 | if (subdev[0]->write_ecc) | ||
687 | concat->mtd.write_ecc = concat_write_ecc; | ||
688 | if (subdev[0]->read_oob) | 726 | if (subdev[0]->read_oob) |
689 | concat->mtd.read_oob = concat_read_oob; | 727 | concat->mtd.read_oob = concat_read_oob; |
690 | if (subdev[0]->write_oob) | 728 | if (subdev[0]->write_oob) |
691 | concat->mtd.write_oob = concat_write_oob; | 729 | concat->mtd.write_oob = concat_write_oob; |
730 | if (subdev[0]->block_isbad) | ||
731 | concat->mtd.block_isbad = concat_block_isbad; | ||
732 | if (subdev[0]->block_markbad) | ||
733 | concat->mtd.block_markbad = concat_block_markbad; | ||
734 | |||
735 | concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; | ||
692 | 736 | ||
693 | concat->subdev[0] = subdev[0]; | 737 | concat->subdev[0] = subdev[0]; |
694 | 738 | ||
@@ -717,12 +761,12 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c | |||
717 | subdev[i]->flags & MTD_WRITEABLE; | 761 | subdev[i]->flags & MTD_WRITEABLE; |
718 | } | 762 | } |
719 | concat->mtd.size += subdev[i]->size; | 763 | concat->mtd.size += subdev[i]->size; |
720 | if (concat->mtd.oobblock != subdev[i]->oobblock || | 764 | concat->mtd.ecc_stats.badblocks += |
765 | subdev[i]->ecc_stats.badblocks; | ||
766 | if (concat->mtd.writesize != subdev[i]->writesize || | ||
721 | concat->mtd.oobsize != subdev[i]->oobsize || | 767 | concat->mtd.oobsize != subdev[i]->oobsize || |
722 | concat->mtd.ecctype != subdev[i]->ecctype || | 768 | concat->mtd.ecctype != subdev[i]->ecctype || |
723 | concat->mtd.eccsize != subdev[i]->eccsize || | 769 | concat->mtd.eccsize != subdev[i]->eccsize || |
724 | !concat->mtd.read_ecc != !subdev[i]->read_ecc || | ||
725 | !concat->mtd.write_ecc != !subdev[i]->write_ecc || | ||
726 | !concat->mtd.read_oob != !subdev[i]->read_oob || | 770 | !concat->mtd.read_oob != !subdev[i]->read_oob || |
727 | !concat->mtd.write_oob != !subdev[i]->write_oob) { | 771 | !concat->mtd.write_oob != !subdev[i]->write_oob) { |
728 | kfree(concat); | 772 | kfree(concat); |
@@ -734,14 +778,11 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c | |||
734 | 778 | ||
735 | } | 779 | } |
736 | 780 | ||
781 | concat->mtd.ecclayout = subdev[0]->ecclayout; | ||
782 | |||
737 | concat->num_subdev = num_devs; | 783 | concat->num_subdev = num_devs; |
738 | concat->mtd.name = name; | 784 | concat->mtd.name = name; |
739 | 785 | ||
740 | /* | ||
741 | * NOTE: for now, we do not provide any readv()/writev() methods | ||
742 | * because they are messy to implement and they are not | ||
743 | * used to a great extent anyway. | ||
744 | */ | ||
745 | concat->mtd.erase = concat_erase; | 786 | concat->mtd.erase = concat_erase; |
746 | concat->mtd.read = concat_read; | 787 | concat->mtd.read = concat_read; |
747 | concat->mtd.write = concat_write; | 788 | concat->mtd.write = concat_write; |