2021-05-06 19:58:51

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH 6/6] mtd: spi-nor: core; avoid odd length/address writes in 8D-8D-8D mode

On Octal DTR capable flashes like Micron Xcella the writes cannot start
or end at an odd address in Octal DTR mode. Extra 0xff bytes need to be
appended or prepended to make sure the start address and end address are
even. 0xff is used because on NOR flashes a program operation can only
flip bits from 1 to 0, not the other way round. 0 to 1 flip needs to
happen via erases.

Signed-off-by: Pratyush Yadav <[email protected]>

---

drivers/mtd/spi-nor/core.c | 72 +++++++++++++++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 3d66cc34af4d..265d8b25fc7f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2022,6 +2022,71 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
return ret;
}

+/*
+ * On Octal DTR capable flashes like Micron Xcella the writes cannot start or
+ * end at an odd address in Octal DTR mode. Extra 0xff bytes need to be appended
+ * or prepended to make sure the start address and end address are even. 0xff is
+ * used because on NOR flashes a program operation can only flip bits from 1 to
+ * 0, not the other way round. 0 to 1 flip needs to happen via erases.
+ */
+static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
+ const u8 *buf)
+{
+ u8 *tmp_buf;
+ size_t bytes_written;
+ loff_t start, end;
+ int ret;
+
+ if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_write_data(nor, to, len, buf);
+
+ tmp_buf = kmalloc(nor->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ memset(tmp_buf, 0xff, nor->page_size);
+
+ start = round_down(to, 2);
+ end = round_up(to + len, 2);
+
+ memcpy(tmp_buf + (to - start), buf, len);
+
+ ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are written than actually requested, but that number can't
+ * be reported to the calling function or it will confuse its
+ * calculations. Calculate how many of the _requested_ bytes were
+ * written.
+ */
+ bytes_written = ret;
+
+ if (to != start)
+ ret -= to - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually
+ * written. For example, if for some reason the controller could only
+ * complete a partial write then the adjustment for the extra bytes at
+ * the end is not needed.
+ */
+ if (start + bytes_written == end)
+ ret -= end - (to + len);
+
+ if (ret < 0)
+ ret = -EIO;
+
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
/*
* Write an address range to the nor chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
@@ -2066,7 +2131,12 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
if (ret)
goto write_err;

- ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
+ if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
+ buf + i);
+ else
+ ret = spi_nor_write_data(nor, addr, page_remain,
+ buf + i);
if (ret < 0)
goto write_err;
written = ret;
--
2.30.0


2021-05-07 17:33:19

by Michael Walle

[permalink] [raw]
Subject: Re: [PATCH 6/6] mtd: spi-nor: core; avoid odd length/address writes in 8D-8D-8D mode

Am 2021-05-06 21:18, schrieb Pratyush Yadav:
> On Octal DTR capable flashes like Micron Xcella the writes cannot start
> or end at an odd address in Octal DTR mode. Extra 0xff bytes need to be
> appended or prepended to make sure the start address and end address
> are
> even. 0xff is used because on NOR flashes a program operation can only
> flip bits from 1 to 0, not the other way round. 0 to 1 flip needs to
> happen via erases.
>
> Signed-off-by: Pratyush Yadav <[email protected]>
>
> ---
>
> drivers/mtd/spi-nor/core.c | 72 +++++++++++++++++++++++++++++++++++++-
> 1 file changed, 71 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 3d66cc34af4d..265d8b25fc7f 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -2022,6 +2022,71 @@ static int spi_nor_read(struct mtd_info *mtd,
> loff_t from, size_t len,
> return ret;
> }
>
> +/*
> + * On Octal DTR capable flashes like Micron Xcella the writes cannot
> start or
> + * end at an odd address in Octal DTR mode. Extra 0xff bytes need to
> be appended
> + * or prepended to make sure the start address and end address are
> even. 0xff is
> + * used because on NOR flashes a program operation can only flip bits
> from 1 to
> + * 0, not the other way round. 0 to 1 flip needs to happen via erases.
> + */
> +static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to,
> size_t len,
> + const u8 *buf)
> +{
> + u8 *tmp_buf;
> + size_t bytes_written;
> + loff_t start, end;
> + int ret;
> +
> + if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
> + return spi_nor_write_data(nor, to, len, buf);
> +
> + tmp_buf = kmalloc(nor->page_size, GFP_KERNEL);
> + if (!tmp_buf)
> + return -ENOMEM;
> +
> + memset(tmp_buf, 0xff, nor->page_size);

This could be replaced by just setting the first and the
last byte to 0xff. But this might be easier to read. I am
fine with both.

> +
> + start = round_down(to, 2);
> + end = round_up(to + len, 2);
> +
> + memcpy(tmp_buf + (to - start), buf, len);
> +
> + ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
> + if (ret == 0) {
> + ret = -EIO;
> + goto out;
> + }
else if ? I've missed this in the other patch.

> + if (ret < 0)
> + goto out;
> +
> + /*
> + * More bytes are written than actually requested, but that number
> can't
> + * be reported to the calling function or it will confuse its
> + * calculations. Calculate how many of the _requested_ bytes were
> + * written.
> + */
> + bytes_written = ret;
> +
> + if (to != start)
> + ret -= to - start;
> +
> + /*
> + * Only account for extra bytes at the end if they were actually
> + * written. For example, if for some reason the controller could only
> + * complete a partial write then the adjustment for the extra bytes
> at
> + * the end is not needed.
> + */
> + if (start + bytes_written == end)
> + ret -= end - (to + len);
> +
> + if (ret < 0)
> + ret = -EIO;

can this happen?

> +
> +out:
> + kfree(tmp_buf);
> + return ret;
> +}
> +
> /*
> * Write an address range to the nor chip. Data must be written in
> * FLASH_PAGESIZE chunks. The address range may be any size provided
> @@ -2066,7 +2131,12 @@ static int spi_nor_write(struct mtd_info *mtd,
> loff_t to, size_t len,
> if (ret)
> goto write_err;
>
> - ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
> + if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
> + ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
> + buf + i);
> + else
> + ret = spi_nor_write_data(nor, addr, page_remain,
> + buf + i);
> if (ret < 0)
> goto write_err;
> written = ret;

-michael

2021-05-07 17:40:15

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH 6/6] mtd: spi-nor: core; avoid odd length/address writes in 8D-8D-8D mode

On 07/05/21 05:56PM, Michael Walle wrote:
> Am 2021-05-06 21:18, schrieb Pratyush Yadav:
> > On Octal DTR capable flashes like Micron Xcella the writes cannot start
> > or end at an odd address in Octal DTR mode. Extra 0xff bytes need to be
> > appended or prepended to make sure the start address and end address are
> > even. 0xff is used because on NOR flashes a program operation can only
> > flip bits from 1 to 0, not the other way round. 0 to 1 flip needs to
> > happen via erases.
> >
> > Signed-off-by: Pratyush Yadav <[email protected]>
> >
> > ---
> >
> > drivers/mtd/spi-nor/core.c | 72 +++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 71 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> > index 3d66cc34af4d..265d8b25fc7f 100644
> > --- a/drivers/mtd/spi-nor/core.c
> > +++ b/drivers/mtd/spi-nor/core.c
> > @@ -2022,6 +2022,71 @@ static int spi_nor_read(struct mtd_info *mtd,
> > loff_t from, size_t len,
> > return ret;
> > }
> >
> > +/*
> > + * On Octal DTR capable flashes like Micron Xcella the writes cannot
> > start or
> > + * end at an odd address in Octal DTR mode. Extra 0xff bytes need to
> > be appended
> > + * or prepended to make sure the start address and end address are
> > even. 0xff is
> > + * used because on NOR flashes a program operation can only flip bits
> > from 1 to
> > + * 0, not the other way round. 0 to 1 flip needs to happen via erases.
> > + */
> > +static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to,
> > size_t len,
> > + const u8 *buf)
> > +{
> > + u8 *tmp_buf;
> > + size_t bytes_written;
> > + loff_t start, end;
> > + int ret;
> > +
> > + if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
> > + return spi_nor_write_data(nor, to, len, buf);
> > +
> > + tmp_buf = kmalloc(nor->page_size, GFP_KERNEL);
> > + if (!tmp_buf)
> > + return -ENOMEM;
> > +
> > + memset(tmp_buf, 0xff, nor->page_size);
>
> This could be replaced by just setting the first and the
> last byte to 0xff. But this might be easier to read. I am
> fine with both.

First, yes. Not the last. The buffer is allocated to nor->page_size for
simplicity but the write could be smaller than nor->page_size. So you'd
need to calculate the position of the other 0xff byte. It is much
simpler to just initialize the whole buffer. It will be around 256 or
512 bytes so not a big overhead.

>
> > +
> > + start = round_down(to, 2);
> > + end = round_up(to + len, 2);
> > +
> > + memcpy(tmp_buf + (to - start), buf, len);
> > +
> > + ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
> > + if (ret == 0) {
> > + ret = -EIO;
> > + goto out;
> > + }
> else if ? I've missed this in the other patch.

Following the style used in spi_nor_read(). Anyway, I've seen
conflicting advice on which style to be used. Some people don't like
else if when the if ends in a return since it is effectively an else if.
Others like it the other way round. Dunno...

>
> > + if (ret < 0)
> > + goto out;
> > +
> > + /*
> > + * More bytes are written than actually requested, but that number
> > can't
> > + * be reported to the calling function or it will confuse its
> > + * calculations. Calculate how many of the _requested_ bytes were
> > + * written.
> > + */
> > + bytes_written = ret;
> > +
> > + if (to != start)
> > + ret -= to - start;
> > +
> > + /*
> > + * Only account for extra bytes at the end if they were actually
> > + * written. For example, if for some reason the controller could only
> > + * complete a partial write then the adjustment for the extra bytes at
> > + * the end is not needed.
> > + */
> > + if (start + bytes_written == end)
> > + ret -= end - (to + len);
> > +
> > + if (ret < 0)
> > + ret = -EIO;
>
> can this happen?

I don't think so. IIRC this is left over from when I tried a different
approach. Maybe I should change it to WARN_ON() to catch future
programming errors? Though I don't mind if we drop it entirely.

>
> > +
> > +out:
> > + kfree(tmp_buf);
> > + return ret;
> > +}
> > +
> > /*
> > * Write an address range to the nor chip. Data must be written in
> > * FLASH_PAGESIZE chunks. The address range may be any size provided
> > @@ -2066,7 +2131,12 @@ static int spi_nor_write(struct mtd_info *mtd,
> > loff_t to, size_t len,
> > if (ret)
> > goto write_err;
> >
> > - ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
> > + if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
> > + ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
> > + buf + i);
> > + else
> > + ret = spi_nor_write_data(nor, addr, page_remain,
> > + buf + i);
> > if (ret < 0)
> > goto write_err;
> > written = ret;
>
> -michael

Thanks for reviewing.

--
Regards,
Pratyush Yadav
Texas Instruments Inc.