[PATCH v9 03/18] mtd: rawnand: sunxi: use the logical step's OOB length in PIO

From: James Hilliard

Date: Sat Oct 17 2026 - 15:47:23 EST


PIO transfers each logical ECC step through hardware slot zero. On
H6/H616, user-data lengths can differ between logical steps: step zero
can have 32 bytes while a later step has only 16 or none.

sunxi_nfc_hw_ecc_correct() uses the hardware slot to select that length
when filling pattern data. A pattern detected in a later PIO step can
therefore overwrite the following OOB region with step zero's longer
length. The protected-OOB write helper makes the same mistake when
loading slot zero, reading bytes beyond the current step's user data.

Pass the logical step's user-data length explicitly to both helpers.
Separate that length from the user-data register offset: PIO always
starts at register zero, while DMA advances by each preceding step's
length in four-byte words. Update both DMA callers with the helper
contract so variable-length steps continue to use the same registers.
Fixed four-byte layouts and the on-flash geometry are unchanged.

The mismatched logical and hardware indices were found by code inspection
while consolidating the PIO and DMA OOB paths.

Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/mtd/nand/raw/sunxi_nand.c | 106 ++++++++++++--------------------------
1 file changed, 32 insertions(+), 74 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index bd2193d7259d..4ff19da8b98d 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -937,45 +937,22 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)

/* PIO uses register zero; DMA concatenates user data within each transfer. */
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
- int step, bool bbm, int page,
+ unsigned int reg_index, bool bbm, int page,
unsigned int user_data_sz)
{
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ const struct sunxi_nfc_oob_ops *ops = to_sunxi_nand(nand)->oob_ops;
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 user_data;
+ unsigned int i;

- if (!nfc->caps->reg_user_data_len) {
- /*
- * For A10, the user data for step n is in the nth
- * REG_USER_DATA
- */
- user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
- sunxi_nfc_user_data_to_buf(user_data, oob);
- } else {
- /*
- * For H6 NAND controller, the user data for all steps is
- * contained in 32 user data registers, but not at a specific
- * offset for each step, they are just concatenated.
- */
- unsigned int user_data_off = 0;
- unsigned int reg_off;
- u8 *ptr = oob;
- unsigned int i;
-
- for (i = 0; i < step; i++)
- user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
-
- user_data_off /= 4;
- for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
- reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
- user_data = readl(nfc->regs + reg_off);
- sunxi_nfc_user_data_to_buf(user_data, ptr);
- }
+ for (i = 0; i < user_data_sz / 4; i++) {
+ user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, reg_index + i));
+ sunxi_nfc_user_data_to_buf(user_data, oob + i * 4);
}

- /* De-randomize the Bad Block Marker. */
- if (bbm && sunxi_nand->oob_ops->transform_bbm)
- sunxi_nand->oob_ops->transform_bbm(nand, page, oob);
+ /* Undo hardware de-randomization of the plain on-flash BBM. */
+ if (bbm && ops->transform_bbm)
+ ops->transform_bbm(nand, page, oob);
}

/*
@@ -1026,48 +1003,26 @@ static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
}

static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
- const u8 *oob, int step,
- bool bbm, int page)
+ const u8 *oob, unsigned int reg_index,
+ bool bbm, int page,
+ unsigned int user_data_sz)
{
+ const struct sunxi_nfc_oob_ops *ops = to_sunxi_nand(nand)->oob_ops;
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
+ unsigned int i;

- /* Randomize the Bad Block Marker. */
- if (bbm && sunxi_nand->oob_ops->transform_bbm) {
+ /* Compensate hardware randomization so the BBM stays plain on flash. */
+ if (bbm && ops->transform_bbm) {
memcpy(user_data, oob, user_data_sz);
- sunxi_nand->oob_ops->transform_bbm(nand, page, user_data);
+ ops->transform_bbm(nand, page, user_data);
oob = user_data;
}

- if (!nfc->caps->reg_user_data_len) {
- /*
- * For A10, the user data for step n is in the nth
- * REG_USER_DATA
- */
- writel(sunxi_nfc_buf_to_user_data(oob),
- nfc->regs + NFC_REG_USER_DATA(nfc, step));
- } else {
- /*
- * For H6 NAND controller, the user data for all steps is
- * contained in 32 user data registers, but not at a specific
- * offset for each step, they are just concatenated.
- */
- unsigned int user_data_off = 0;
- const u8 *ptr = oob;
- unsigned int i;
-
- for (i = 0; i < step; i++)
- user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
-
- user_data_off /= 4;
- for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
- writel(sunxi_nfc_buf_to_user_data(ptr),
- nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
- }
+ for (i = 0; i < user_data_sz / 4; i++) {
+ writel(sunxi_nfc_buf_to_user_data(oob + i * 4),
+ nfc->regs + NFC_REG_USER_DATA(nfc, reg_index + i));
}
-
}

static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
@@ -1120,11 +1075,9 @@ struct sunxi_nfc_ecc_read {

static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, u32 pattern_found,
- bool *erased)
+ unsigned int user_data_sz, bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 tmp;

@@ -1223,10 +1176,10 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
- unsigned int user_data_sz;
+ unsigned int reg_index = 0, user_data_sz;
int i, ret;

- for (i = 0; i < nsteps; i++) {
+ for (i = 0; i < nsteps; i++, reg_index += user_data_sz / 4) {
int step = first_step + i;
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
u8 *data = read->buf + step * ecc->size;
@@ -1238,7 +1191,8 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
bitflips = sunxi_nfc_hw_ecc_correct(nand,
!read->dma || randomized ? data : NULL,
read->oob_required ? oob : NULL,
- i, status, pattern_found, &erased);
+ i, status, pattern_found,
+ user_data_sz, &erased);
if (bitflips < 0) {
/* Preserve PIO data before the raw erased-page check. */
if (!read->dma && !randomized)
@@ -1267,7 +1221,7 @@ static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
true, read->page);
}

- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !step,
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, reg_index, !step,
read->page, user_data_sz);
}

@@ -1456,7 +1410,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page,
+ user_data_sz);

writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR | NFC_ECC_OP,
@@ -1682,6 +1637,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct scatterlist sg;
+ unsigned int reg_index = 0;
u32 wait;
int ret, i;

@@ -1702,8 +1658,10 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *oob = nand->oob_poi + oob_off;

- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, reg_index, !i, page,
+ user_data_sz);
sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
+ reg_index += user_data_sz / 4;
}

nand_prog_page_begin_op(nand, page, 0, NULL, 0);

--
2.53.0