aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bernacki <gber@FreeBSD.org>2013-11-20 11:10:23 +0000
committerGrzegorz Bernacki <gber@FreeBSD.org>2013-11-20 11:10:23 +0000
commit8e16e025ac12e70752d4438ae63b9b47f15a55d1 (patch)
tree09df9c0791497f4f96cf7e604bf502fb443608c6
parent79e9451f0730b6309d5d9fac8573fa862f32895a (diff)
downloadsrc-8e16e025ac12e70752d4438ae63b9b47f15a55d1.tar.gz
src-8e16e025ac12e70752d4438ae63b9b47f15a55d1.zip
Split raw reading/programming into smaller chunks to avoid allocating too
big chunk of kernel memory. Validate size of data. Add error handling to avoid calling copyout() when data has not been read correctly. Reviewed by: zbb Reported by: x90c <geinblues@gmail.com> MFC after: 2 days
Notes
Notes: svn path=/head/; revision=258387
-rw-r--r--sys/dev/nand/nand_cdev.c60
-rw-r--r--sys/dev/nand/nand_geom.c58
2 files changed, 100 insertions, 18 deletions
diff --git a/sys/dev/nand/nand_cdev.c b/sys/dev/nand/nand_cdev.c
index ac27ff361537..bcfe3ea36125 100644
--- a/sys/dev/nand/nand_cdev.c
+++ b/sys/dev/nand/nand_cdev.c
@@ -294,19 +294,39 @@ nand_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
struct thread *td)
{
struct nand_chip *chip;
+ struct chip_geom *cg;
struct nand_oob_rw *oob_rw = NULL;
struct nand_raw_rw *raw_rw = NULL;
device_t nandbus;
+ size_t bufsize, len, raw_size;
+ off_t off;
uint8_t *buf = NULL;
int ret = 0;
uint8_t status;
chip = (struct nand_chip *)dev->si_drv1;
+ cg = &chip->chip_geom;
nandbus = device_get_parent(chip->dev);
if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) {
raw_rw = (struct nand_raw_rw *)data;
- buf = malloc(raw_rw->len, M_NAND, M_WAITOK);
+ raw_size = cg->pgs_per_blk * (cg->page_size + cg->oob_size);
+
+ /* Check if len is not bigger than chip size */
+ if (raw_rw->len > raw_size)
+ return (EFBIG);
+
+ /*
+ * Do not ask for too much memory, in case of large transfers
+ * read/write in 16-pages chunks
+ */
+ bufsize = 16 * (cg->page_size + cg->oob_size);
+ if (raw_rw->len < bufsize)
+ bufsize = raw_rw->len;
+
+ buf = malloc(bufsize, M_NAND, M_WAITOK);
+ len = raw_rw->len;
+ off = 0;
}
switch(cmd) {
case NAND_IO_ERASE:
@@ -335,19 +355,37 @@ nand_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
break;
case NAND_IO_RAW_PROG:
- ret = copyin(raw_rw->data, buf, raw_rw->len);
- if (ret)
- break;
- ret = nand_prog_pages_raw(chip, raw_rw->off, buf,
- raw_rw->len);
+ while (len > 0) {
+ if (len < bufsize)
+ bufsize = len;
+ ret = copyin(raw_rw->data + off, buf, bufsize);
+ if (ret)
+ break;
+ ret = nand_prog_pages_raw(chip, raw_rw->off + off, buf,
+ bufsize);
+ if (ret)
+ break;
+ len -= bufsize;
+ off += bufsize;
+ }
break;
case NAND_IO_RAW_READ:
- ret = nand_read_pages_raw(chip, raw_rw->off, buf,
- raw_rw->len);
- if (ret)
- break;
- ret = copyout(buf, raw_rw->data, raw_rw->len);
+ while (len > 0) {
+ if (len < bufsize)
+ bufsize = len;
+
+ ret = nand_read_pages_raw(chip, raw_rw->off + off, buf,
+ bufsize);
+ if (ret)
+ break;
+
+ ret = copyout(buf, raw_rw->data + off, bufsize);
+ if (ret)
+ break;
+ len -= bufsize;
+ off += bufsize;
+ }
break;
case NAND_IO_PAGE_STAT:
diff --git a/sys/dev/nand/nand_geom.c b/sys/dev/nand/nand_geom.c
index 2c4915b319bf..f17ee60959bb 100644
--- a/sys/dev/nand/nand_geom.c
+++ b/sys/dev/nand/nand_geom.c
@@ -193,20 +193,41 @@ nand_ioctl(struct disk *ndisk, u_long cmd, void *data, int fflag,
struct thread *td)
{
struct nand_chip *chip;
+ struct chip_geom *cg;
struct nand_oob_rw *oob_rw = NULL;
struct nand_raw_rw *raw_rw = NULL;
device_t nandbus;
+ size_t bufsize, len, raw_size;
+ off_t off;
uint8_t *buf = NULL;
int ret = 0;
uint8_t status;
chip = (struct nand_chip *)ndisk->d_drv1;
+ cg = &chip->chip_geom;
nandbus = device_get_parent(chip->dev);
if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) {
raw_rw = (struct nand_raw_rw *)data;
- buf = malloc(raw_rw->len, M_NAND, M_WAITOK);
+ raw_size = cg->pgs_per_blk * (cg->page_size + cg->oob_size);
+
+ /* Check if len is not bigger than chip size */
+ if (raw_rw->len > raw_size)
+ return (EFBIG);
+
+ /*
+ * Do not ask for too much memory, in case of large transfers
+ * read/write in 16-pages chunks
+ */
+ bufsize = 16 * (cg->page_size + cg->oob_size);
+ if (raw_rw->len < bufsize)
+ bufsize = raw_rw->len;
+
+ buf = malloc(bufsize, M_NAND, M_WAITOK);
+ len = raw_rw->len;
+ off = 0;
}
+
switch (cmd) {
case NAND_IO_ERASE:
ret = nand_erase_blocks(chip, ((off_t *)data)[0],
@@ -234,15 +255,38 @@ nand_ioctl(struct disk *ndisk, u_long cmd, void *data, int fflag,
break;
case NAND_IO_RAW_PROG:
- copyin(raw_rw->data, buf, raw_rw->len);
- ret = nand_prog_pages_raw(chip, raw_rw->off, buf,
- raw_rw->len);
+ while (len > 0) {
+ if (len < bufsize)
+ bufsize = len;
+
+ ret = copyin(raw_rw->data + off, buf, bufsize);
+ if (ret)
+ break;
+ ret = nand_prog_pages_raw(chip, raw_rw->off + off, buf,
+ bufsize);
+ if (ret)
+ break;
+ len -= bufsize;
+ off += bufsize;
+ }
break;
case NAND_IO_RAW_READ:
- ret = nand_read_pages_raw(chip, raw_rw->off, buf,
- raw_rw->len);
- copyout(buf, raw_rw->data, raw_rw->len);
+ while (len > 0) {
+ if (len < bufsize)
+ bufsize = len;
+
+ ret = nand_read_pages_raw(chip, raw_rw->off + off, buf,
+ bufsize);
+ if (ret)
+ break;
+
+ ret = copyout(buf, raw_rw->data + off, bufsize);
+ if (ret)
+ break;
+ len -= bufsize;
+ off += bufsize;
+ }
break;
case NAND_IO_GET_CHIP_PARAM: