Skip to content

Commit a056393

Browse files
Pratyush Yadavgregkh
authored andcommitted
mtd: spi-nor: core: avoid odd length/address reads on 8D-8D-8D mode
commit f156b23 upstream. On Octal DTR capable flashes like Micron Xcella reads cannot start or end at an odd address in Octal DTR mode. Extra bytes need to be read at the start or end to make sure both the start address and length remain even. To avoid allocating too much extra memory, thereby putting unnecessary memory pressure on the system, the temporary buffer containing the extra padding bytes is capped at PAGE_SIZE bytes. The rest of the 2-byte aligned part should be read directly in the main buffer. Signed-off-by: Pratyush Yadav <p.yadav@ti.com> Reviewed-by: Michael Walle <michael@walle.cc> Signed-off-by: Luke Wang <ziniu.wang_1@nxp.com> Signed-off-by: Pratyush Yadav <pratyush@kernel.org> Link: https://lore.kernel.org/r/20250708091646.292-1-ziniu.wang_1@nxp.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 9956d48 commit a056393

1 file changed

Lines changed: 75 additions & 1 deletion

File tree

drivers/mtd/spi-nor/core.c

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,76 @@ static const struct flash_info *spi_nor_detect(struct spi_nor *nor)
20372037
return info;
20382038
}
20392039

2040+
/*
2041+
* On Octal DTR capable flashes, reads cannot start or end at an odd
2042+
* address in Octal DTR mode. Extra bytes need to be read at the start
2043+
* or end to make sure both the start address and length remain even.
2044+
*/
2045+
static int spi_nor_octal_dtr_read(struct spi_nor *nor, loff_t from, size_t len,
2046+
u_char *buf)
2047+
{
2048+
u_char *tmp_buf;
2049+
size_t tmp_len;
2050+
loff_t start, end;
2051+
int ret, bytes_read;
2052+
2053+
if (IS_ALIGNED(from, 2) && IS_ALIGNED(len, 2))
2054+
return spi_nor_read_data(nor, from, len, buf);
2055+
else if (IS_ALIGNED(from, 2) && len > PAGE_SIZE)
2056+
return spi_nor_read_data(nor, from, round_down(len, PAGE_SIZE),
2057+
buf);
2058+
2059+
tmp_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
2060+
if (!tmp_buf)
2061+
return -ENOMEM;
2062+
2063+
start = round_down(from, 2);
2064+
end = round_up(from + len, 2);
2065+
2066+
/*
2067+
* Avoid allocating too much memory. The requested read length might be
2068+
* quite large. Allocating a buffer just as large (slightly bigger, in
2069+
* fact) would put unnecessary memory pressure on the system.
2070+
*
2071+
* For example if the read is from 3 to 1M, then this will read from 2
2072+
* to 4098. The reads from 4098 to 1M will then not need a temporary
2073+
* buffer so they can proceed as normal.
2074+
*/
2075+
tmp_len = min_t(size_t, end - start, PAGE_SIZE);
2076+
2077+
ret = spi_nor_read_data(nor, start, tmp_len, tmp_buf);
2078+
if (ret == 0) {
2079+
ret = -EIO;
2080+
goto out;
2081+
}
2082+
if (ret < 0)
2083+
goto out;
2084+
2085+
/*
2086+
* More bytes are read than actually requested, but that number can't be
2087+
* reported to the calling function or it will confuse its calculations.
2088+
* Calculate how many of the _requested_ bytes were read.
2089+
*/
2090+
bytes_read = ret;
2091+
2092+
if (from != start)
2093+
ret -= from - start;
2094+
2095+
/*
2096+
* Only account for extra bytes at the end if they were actually read.
2097+
* For example, if the total length was truncated because of temporary
2098+
* buffer size limit then the adjustment for the extra bytes at the end
2099+
* is not needed.
2100+
*/
2101+
if (start + bytes_read == end)
2102+
ret -= end - (from + len);
2103+
2104+
memcpy(buf, tmp_buf + (from - start), ret);
2105+
out:
2106+
kfree(tmp_buf);
2107+
return ret;
2108+
}
2109+
20402110
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
20412111
size_t *retlen, u_char *buf)
20422112
{
@@ -2054,7 +2124,11 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
20542124
while (len) {
20552125
loff_t addr = from;
20562126

2057-
ret = spi_nor_read_data(nor, addr, len, buf);
2127+
if (nor->read_proto == SNOR_PROTO_8_8_8_DTR)
2128+
ret = spi_nor_octal_dtr_read(nor, addr, len, buf);
2129+
else
2130+
ret = spi_nor_read_data(nor, addr, len, buf);
2131+
20582132
if (ret == 0) {
20592133
/* We shouldn't see 0-length reads */
20602134
ret = -EIO;

0 commit comments

Comments
 (0)