[][SW framework][common][bsp][add panic write support for nmbm]

[Description]
Add panic write support for nmbm

[Release-log]
N/A

Change-Id: I9055348d401335aac31f8aacdf0cac16c1af873c
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7654466
diff --git a/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-core.c b/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-core.c
index b80ea42..9dc4a10 100644
--- a/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-core.c
+++ b/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-core.c
@@ -257,6 +257,37 @@
 }
 
 /*
+ * nmbm_panic_write_phys_page - Panic write page with retry
+ * @ni: NMBM instance structure
+ * @addr: linear address where the data will be written to
+ * @data: the main data to be written
+ *
+ * Write a page for at most NMBM_TRY_COUNT times.
+ */
+static bool nmbm_panic_write_phys_page(struct nmbm_instance *ni, uint64_t addr,
+				       const void *data)
+{
+	int tries, ret;
+
+	if (ni->lower.flags & NMBM_F_READ_ONLY) {
+		nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr);
+		return false;
+	}
+
+	for (tries = 0; tries < NMBM_TRY_COUNT; tries++) {
+		ret = ni->lower.panic_write_page(ni->lower.arg, addr, data);
+		if (!ret)
+			return true;
+
+		nmbm_reset_chip(ni);
+	}
+
+	nlog_err(ni, "Panic page write failed at address 0x%08llx\n", addr);
+
+	return false;
+}
+
+/*
  * nmbm_erase_phys_block - Erase a block with retry
  * @ni: NMBM instance structure
  * @addr: Linear address
@@ -2730,6 +2761,56 @@
 }
 
 /*
+ * nmbm_panic_write_logic_page - Panic write page based on logic address
+ * @ni: NMBM instance structure
+ * @addr: logic linear address
+ * @data: buffer contains main data. optional.
+ */
+static int nmbm_panic_write_logic_page(struct nmbm_instance *ni, uint64_t addr,
+				       const void *data)
+{
+	uint32_t lb, pb, offset;
+	uint64_t paddr;
+	bool success;
+
+	if (!ni->lower.panic_write_page)
+		return -ENOTSUPP;
+
+	/* Extract block address and in-block offset */
+	lb = addr2ba(ni, addr);
+	offset = addr & ni->erasesize_mask;
+
+	/* Map logic block to physical block */
+	pb = ni->block_mapping[lb];
+
+	/* Whether the logic block is good (has valid mapping) */
+	if ((int32_t)pb < 0) {
+		nlog_debug(ni, "Logic block %u is a bad block\n", lb);
+		return -EIO;
+	}
+
+	/* Fail if physical block is marked bad */
+	if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD)
+		return -EIO;
+
+	/* Assemble new address */
+	paddr = ba2addr(ni, pb) + offset;
+
+	success = nmbm_panic_write_phys_page(ni, paddr, data);
+	if (success)
+		return 0;
+
+	/*
+	 * Do not remap bad block here. Just mark this block in state table.
+	 * Remap this block on erasing.
+	 */
+	nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP);
+	nmbm_update_info_table(ni);
+
+	return -EIO;
+}
+
+/*
  * nmbm_write_single_page - Write one page based on logic address
  * @ni: NMBM instance structure
  * @addr: logic linear address
@@ -2759,6 +2840,32 @@
 }
 
 /*
+ * nmbm_panic_write_single_page - Panic write one page based on logic address
+ * @ni: NMBM instance structure
+ * @addr: logic linear address
+ * @data: buffer contains main data. optional.
+ */
+int nmbm_panic_write_single_page(struct nmbm_instance *ni, uint64_t addr,
+				 const void *data)
+{
+	if (!ni)
+		return -EINVAL;
+
+	/* Sanity check */
+	if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) {
+		nlog_debug(ni, "Device is forced read-only\n");
+		return -EROFS;
+	}
+
+	if (addr >= ba2addr(ni, ni->data_block_count)) {
+		nlog_err(ni, "Address 0x%llx is invalid\n", addr);
+		return -EINVAL;
+	}
+
+	return nmbm_panic_write_logic_page(ni, addr, data);
+}
+
+/*
  * nmbm_write_range - Write data without oob
  * @ni: NMBM instance structure
  * @addr: logic linear address
diff --git a/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-mtd.c b/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-mtd.c
index a3e9e18..94413a3 100644
--- a/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-mtd.c
+++ b/target/linux/generic/files-5.4/drivers/mtd/nmbm/nmbm-mtd.c
@@ -141,6 +141,16 @@
 	return mtd_write_oob(nm->lower, addr, &ops);
 }
 
+static int nmbm_lower_panic_write_page(void *arg, uint64_t addr,
+				       const void *buf)
+{
+	struct nmbm_mtd *nm = arg;
+	size_t retlen;
+
+	return mtd_panic_write(nm->lower, addr, nm->lower->writesize, &retlen,
+			       buf);
+}
+
 static int nmbm_lower_erase_block(void *arg, uint64_t addr)
 {
 	struct nmbm_mtd *nm = arg;
@@ -538,6 +548,44 @@
 	return ret;
 }
 
+static int nmbm_mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
+				size_t *retlen, const u_char *buf)
+{
+	struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
+	size_t chklen, wrlen = 0;
+	uint32_t col;
+	int ret;
+
+	col = to & nm->lower->writesize_mask;
+	to &= ~nm->lower->writesize_mask;
+
+	while (len) {
+		/* Move data */
+		chklen = nm->lower->writesize - col;
+		if (chklen > len)
+			chklen = len;
+
+		if (chklen < nm->lower->writesize)
+			memset(nm->page_cache, 0xff, nm->lower->writesize);
+		memcpy(nm->page_cache + col, buf + wrlen, chklen);
+
+		len -= chklen;
+		col = 0; /* (col + chklen) %  */
+		wrlen += chklen;
+
+		ret = nmbm_panic_write_single_page(nm->ni, to, nm->page_cache);
+		if (ret)
+			break;
+
+		to += nm->lower->writesize;
+	}
+
+	if (retlen)
+		*retlen = wrlen;
+
+	return 0;
+}
+
 static int nmbm_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
 {
 	struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper);
@@ -634,6 +682,7 @@
 
 	nld.read_page = nmbm_lower_read_page;
 	nld.write_page = nmbm_lower_write_page;
+	nld.panic_write_page = nmbm_lower_panic_write_page;
 	nld.erase_block = nmbm_lower_erase_block;
 	nld.is_bad_block = nmbm_lower_is_bad_block;
 	nld.mark_bad_block = nmbm_lower_mark_bad_block;
@@ -697,6 +746,7 @@
 	mtd->_erase = nmbm_mtd_erase;
 	mtd->_read_oob = nmbm_mtd_read_oob;
 	mtd->_write_oob = nmbm_mtd_write_oob;
+	mtd->_panic_write = nmbm_mtd_panic_write;
 	mtd->_block_isbad = nmbm_mtd_block_isbad;
 	mtd->_block_markbad = nmbm_mtd_block_markbad;
 	mtd->_reboot = nmbm_mtd_shutdown;
diff --git a/target/linux/generic/files-5.4/include/nmbm/nmbm.h b/target/linux/generic/files-5.4/include/nmbm/nmbm.h
index c040098..0f43466 100644
--- a/target/linux/generic/files-5.4/include/nmbm/nmbm.h
+++ b/target/linux/generic/files-5.4/include/nmbm/nmbm.h
@@ -52,6 +52,7 @@
 	 */
 	int (*read_page)(void *arg, uint64_t addr, void *buf, void *oob, enum nmbm_oob_mode mode);
 	int (*write_page)(void *arg, uint64_t addr, const void *buf, const void *oob, enum nmbm_oob_mode mode);
+	int (*panic_write_page)(void *arg, uint64_t addr, const void *buf);
 	int (*erase_block)(void *arg, uint64_t addr);
 
 	int (*is_bad_block)(void *arg, uint64_t addr);
@@ -88,6 +89,8 @@
 int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr,
 			   const void *data, const void *oob,
 			   enum nmbm_oob_mode mode);
+int nmbm_panic_write_single_page(struct nmbm_instance *ni, uint64_t addr,
+				 const void *data);
 int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size,
 		     const void *data, enum nmbm_oob_mode mode,
 		     size_t *retlen);