📄 mtdconcat.c
字号:
/* * MTD device concatenation layer * * (C) 2002 Robert Kaiser <rkaiser@sysgo.de> * * NAND support by Christian Gan <cgan@iders.ca> * * This code is GPL */#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/types.h>#include <linux/mtd/mtd.h>#include <linux/mtd/concat.h>#include <asm/div64.h>/* * Our storage structure: * Subdev points to an array of pointers to struct mtd_info objects * which is allocated along with this structure * */struct mtd_concat { struct mtd_info mtd; int num_subdev; struct mtd_info **subdev;};/* * how to calculate the size required for the above structure, * including the pointer array subdev points to: */#define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \ ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))/* * Given a pointer to the MTD object in the mtd_concat structure, * we can retrieve the pointer to that structure with this macro. */#define CONCAT(x) ((struct mtd_concat *)(x))/* * MTD methods which look up the relevant subdevice, translate the * effective address and pass through to the subdevice. */static intconcat_read(struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf){ struct mtd_concat *concat = CONCAT(mtd); int ret = 0, err; int i; *retlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; size_t size, retsize; if (from >= subdev->size) { /* Not destined for this subdev */ size = 0; from -= subdev->size; continue; } if (from + len > subdev->size) /* First part goes into this subdev */ size = subdev->size - from; else /* Entire transaction goes into this subdev */ size = len; err = subdev->read(subdev, from, size, &retsize, buf); /* Save information about bitflips! */ if (unlikely(err)) { if (err == -EBADMSG) { mtd->ecc_stats.failed++; ret = err; } else if (err == -EUCLEAN) { mtd->ecc_stats.corrected++; /* Do not overwrite -EBADMSG !! */ if (!ret) ret = err; } else return err; } *retlen += retsize; len -= size; if (len == 0) return ret; buf += size; from = 0; } return -EINVAL;}static intconcat_write(struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf){ struct mtd_concat *concat = CONCAT(mtd); int err = -EINVAL; int i; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; *retlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; size_t size, retsize; if (to >= subdev->size) { size = 0; to -= subdev->size; continue; } if (to + len > subdev->size) size = subdev->size - to; else size = len; if (!(subdev->flags & MTD_WRITEABLE)) err = -EROFS; else err = subdev->write(subdev, to, size, &retsize, buf); if (err) break; *retlen += retsize; len -= size; if (len == 0) break; err = -EINVAL; buf += size; to = 0; } return err;}static intconcat_writev(struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t * retlen){ struct mtd_concat *concat = CONCAT(mtd); struct kvec *vecs_copy; unsigned long entry_low, entry_high; size_t total_len = 0; int i; int err = -EINVAL; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; *retlen = 0; /* Calculate total length of data */ for (i = 0; i < count; i++) total_len += vecs[i].iov_len; /* Do not allow write past end of device */ if ((to + total_len) > mtd->size) return -EINVAL; /* Check alignment */ if (mtd->writesize > 1) { uint64_t __to = to; if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize)) return -EINVAL; } /* make a copy of vecs */ vecs_copy = kmalloc(sizeof(struct kvec) * count, GFP_KERNEL); if (!vecs_copy) return -ENOMEM; memcpy(vecs_copy, vecs, sizeof(struct kvec) * count); entry_low = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; size_t size, wsize, retsize, old_iov_len; if (to >= subdev->size) { to -= subdev->size; continue; } size = min(total_len, (size_t)(subdev->size - to)); wsize = size; /* store for future use */ entry_high = entry_low; while (entry_high < count) { if (size <= vecs_copy[entry_high].iov_len) break; size -= vecs_copy[entry_high++].iov_len; } old_iov_len = vecs_copy[entry_high].iov_len; vecs_copy[entry_high].iov_len = size; if (!(subdev->flags & MTD_WRITEABLE)) err = -EROFS; else err = subdev->writev(subdev, &vecs_copy[entry_low], entry_high - entry_low + 1, to, &retsize); vecs_copy[entry_high].iov_len = old_iov_len - size; vecs_copy[entry_high].iov_base += size; entry_low = entry_high; if (err) break; *retlen += retsize; total_len -= wsize; if (total_len == 0) break; err = -EINVAL; to = 0; } kfree(vecs_copy); return err;}static intconcat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops){ struct mtd_concat *concat = CONCAT(mtd); struct mtd_oob_ops devops = *ops; int i, err, ret = 0; ops->retlen = ops->oobretlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; if (from >= subdev->size) { from -= subdev->size; continue; } /* partial read ? */ if (from + devops.len > subdev->size) devops.len = subdev->size - from; err = subdev->read_oob(subdev, from, &devops); ops->retlen += devops.retlen; ops->oobretlen += devops.oobretlen; /* Save information about bitflips! */ if (unlikely(err)) { if (err == -EBADMSG) { mtd->ecc_stats.failed++; ret = err; } else if (err == -EUCLEAN) { mtd->ecc_stats.corrected++; /* Do not overwrite -EBADMSG !! */ if (!ret) ret = err; } else return err; } if (devops.datbuf) { devops.len = ops->len - ops->retlen; if (!devops.len) return ret; devops.datbuf += devops.retlen; } if (devops.oobbuf) { devops.ooblen = ops->ooblen - ops->oobretlen; if (!devops.ooblen) return ret; devops.oobbuf += ops->oobretlen; } from = 0; } return -EINVAL;}static intconcat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops){ struct mtd_concat *concat = CONCAT(mtd); struct mtd_oob_ops devops = *ops; int i, err; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; ops->retlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; if (to >= subdev->size) { to -= subdev->size; continue; } /* partial write ? */ if (to + devops.len > subdev->size) devops.len = subdev->size - to; err = subdev->write_oob(subdev, to, &devops); ops->retlen += devops.retlen; if (err) return err; if (devops.datbuf) { devops.len = ops->len - ops->retlen; if (!devops.len) return 0; devops.datbuf += devops.retlen; } if (devops.oobbuf) { devops.ooblen = ops->ooblen - ops->oobretlen; if (!devops.ooblen) return 0; devops.oobbuf += devops.oobretlen; } to = 0; } return -EINVAL;}static void concat_erase_callback(struct erase_info *instr){ wake_up((wait_queue_head_t *) instr->priv);}static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase){ int err; wait_queue_head_t waitq; DECLARE_WAITQUEUE(wait, current); /* * This code was stol^H^H^H^Hinspired by mtdchar.c */ init_waitqueue_head(&waitq); erase->mtd = mtd; erase->callback = concat_erase_callback; erase->priv = (unsigned long) &waitq; /* * FIXME: Allow INTERRUPTIBLE. Which means * not having the wait_queue head on the stack. */ err = mtd->erase(mtd, erase); if (!err) { set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&waitq, &wait); if (erase->state != MTD_ERASE_DONE && erase->state != MTD_ERASE_FAILED) schedule(); remove_wait_queue(&waitq, &wait); set_current_state(TASK_RUNNING); err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0; } return err;}static int concat_erase(struct mtd_info *mtd, struct erase_info *instr){ struct mtd_concat *concat = CONCAT(mtd); struct mtd_info *subdev; int i, err; u_int32_t length, offset = 0; struct erase_info *erase; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; if (instr->addr > concat->mtd.size) return -EINVAL; if (instr->len + instr->addr > concat->mtd.size) return -EINVAL; /* * Check for proper erase block alignment of the to-be-erased area. * It is easier to do this based on the super device's erase * region info rather than looking at each particular sub-device * in turn. */ if (!concat->mtd.numeraseregions) { /* the easy case: device has uniform erase block size */ if (instr->addr & (concat->mtd.erasesize - 1)) return -EINVAL; if (instr->len & (concat->mtd.erasesize - 1)) return -EINVAL; } else { /* device has variable erase size */ struct mtd_erase_region_info *erase_regions = concat->mtd.eraseregions; /* * Find the erase region where the to-be-erased area begins: */ for (i = 0; i < concat->mtd.numeraseregions && instr->addr >= erase_regions[i].offset; i++) ; --i; /* * Now erase_regions[i] is the region in which the * to-be-erased area begins. Verify that the starting * offset is aligned to this region's erase size: */ if (instr->addr & (erase_regions[i].erasesize - 1)) return -EINVAL; /* * now find the erase region where the to-be-erased area ends: */ for (; i < concat->mtd.numeraseregions && (instr->addr + instr->len) >= erase_regions[i].offset; ++i) ; --i; /* * check if the ending offset is aligned to this region's erase size */ if ((instr->addr + instr->len) & (erase_regions[i].erasesize - 1)) return -EINVAL; } instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; /* make a local copy of instr to avoid modifying the caller's struct */ erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); if (!erase) return -ENOMEM; *erase = *instr; length = instr->len; /* * find the subdevice where the to-be-erased area begins, adjust * starting offset to be relative to the subdevice start */ for (i = 0; i < concat->num_subdev; i++) { subdev = concat->subdev[i]; if (subdev->size <= erase->addr) { erase->addr -= subdev->size; offset += subdev->size; } else { break; } } /* must never happen since size limit has been verified above */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -