mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 15:32:33 +00:00
50f7c5af4a
Update to next U-Boot timed release. Remove now obsolete patch 100-01-board-mediatek-add-more-network-configurations.patch Default IP addresses are now dealt with in Kconfig, no longer in board- specific C header files. Add patches to restore ANSI support in bootmenu which was broken upstream, always use high-speed mode on serial UART for improved stability and fix an issue with pinconf not being applied on MT7623 resulting in eMMC being inaccessible when booting from micro SD card. In order to keep the size of the bootloader on MT7623 below 512kB remove some unneeded commands on both MT7623 boards. Tested on: * BananaPi BPi-R2 (MT7623N) * BananaPi BPi-R3 (MT7986A) * BananaPi BPi-R64 (MT7622A) * Linksys E8450 (MT7622B) Signed-off-by: Daniel Golle <daniel@makrotopia.org>
1119 lines
25 KiB
Diff
1119 lines
25 KiB
Diff
From 88271cb3ae9c68dc200d627653df96fc557c2a64 Mon Sep 17 00:00:00 2001
|
|
From: Weijie Gao <weijie.gao@mediatek.com>
|
|
Date: Mon, 25 Jul 2022 10:55:35 +0800
|
|
Subject: [PATCH 47/71] cmd: add a new command for NAND flash debugging
|
|
|
|
Add a command 'nand-ext' for NAND flash debugging:
|
|
- Dump a page with oob, with optional raw read support
|
|
- Display all bad blocks
|
|
- Mark a block as bad block
|
|
- Set a bitflip on a page
|
|
- Erase
|
|
- Read / write data from/to any offset with any size
|
|
- Read / write pages with oob
|
|
- Erase, read and write support skip bad block or forced mode, support
|
|
raw mode, supporot auto-oob mode
|
|
- Supports operating on a specific partition
|
|
- No need to specify NAND device name
|
|
|
|
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
|
|
---
|
|
cmd/Kconfig | 8 +
|
|
cmd/Makefile | 1 +
|
|
cmd/nand-ext.c | 1062 ++++++++++++++++++++++++++++++++++++++++++++++++
|
|
3 files changed, 1071 insertions(+)
|
|
create mode 100644 cmd/nand-ext.c
|
|
|
|
--- a/cmd/Kconfig
|
|
+++ b/cmd/Kconfig
|
|
@@ -1352,6 +1352,14 @@ config CMD_NAND_TORTURE
|
|
|
|
endif # CMD_NAND
|
|
|
|
+config CMD_NAND_EXT
|
|
+ bool "nand - extended nand utility for debugging"
|
|
+ depends on !CMD_NAND
|
|
+ default y if MTD_RAW_NAND || MTD_SPI_NAND || MTK_SPI_NAND
|
|
+ select MTD_PARTITIONS
|
|
+ help
|
|
+ NAND flash R/W and debugging support.
|
|
+
|
|
config CMD_NMBM
|
|
depends on NMBM_MTD
|
|
bool "nmbm"
|
|
--- a/cmd/Makefile
|
|
+++ b/cmd/Makefile
|
|
@@ -123,6 +123,7 @@ obj-y += legacy-mtd-utils.o
|
|
endif
|
|
obj-$(CONFIG_CMD_MUX) += mux.o
|
|
obj-$(CONFIG_CMD_NAND) += nand.o
|
|
+obj-$(CONFIG_CMD_NAND_EXT) += nand-ext.o
|
|
obj-$(CONFIG_CMD_NMBM) += nmbm.o
|
|
obj-$(CONFIG_CMD_NET) += net.o
|
|
obj-$(CONFIG_CMD_NVEDIT_EFI) += nvedit_efi.o
|
|
--- /dev/null
|
|
+++ b/cmd/nand-ext.c
|
|
@@ -0,0 +1,1062 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2021 MediaTek Inc. All Rights Reserved.
|
|
+ *
|
|
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
|
|
+ */
|
|
+
|
|
+#include <command.h>
|
|
+#include <stdbool.h>
|
|
+#include <malloc.h>
|
|
+#include <mtd.h>
|
|
+#include <dm/devres.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/mtd/mtd.h>
|
|
+
|
|
+static struct mtd_info *curr_dev;
|
|
+
|
|
+static void mtd_show_parts(struct mtd_info *mtd, int level)
|
|
+{
|
|
+ struct mtd_info *part;
|
|
+ int i;
|
|
+
|
|
+ list_for_each_entry(part, &mtd->partitions, node) {
|
|
+ for (i = 0; i < level; i++)
|
|
+ printf("\t");
|
|
+ printf(" - 0x%012llx-0x%012llx : \"%s\"\n",
|
|
+ part->offset, part->offset + part->size, part->name);
|
|
+
|
|
+ mtd_show_parts(part, level + 1);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void mtd_show_device(struct mtd_info *mtd)
|
|
+{
|
|
+ /* Device */
|
|
+ printf("* %s\n", mtd->name);
|
|
+#if defined(CONFIG_DM)
|
|
+ if (mtd->dev) {
|
|
+ printf(" - device: %s\n", mtd->dev->name);
|
|
+ printf(" - parent: %s\n", mtd->dev->parent->name);
|
|
+ printf(" - driver: %s\n", mtd->dev->driver->name);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* MTD device information */
|
|
+ printf(" - type: ");
|
|
+ switch (mtd->type) {
|
|
+ case MTD_NANDFLASH:
|
|
+ printf("NAND flash\n");
|
|
+ break;
|
|
+ case MTD_MLCNANDFLASH:
|
|
+ printf("MLC NAND flash\n");
|
|
+ break;
|
|
+ case MTD_ABSENT:
|
|
+ default:
|
|
+ printf("Not supported\n");
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ printf(" - block size: 0x%x bytes\n", mtd->erasesize);
|
|
+ printf(" - page size: 0x%x bytes\n", mtd->writesize);
|
|
+ printf(" - OOB size: %u bytes\n", mtd->oobsize);
|
|
+ printf(" - OOB available: %u bytes\n", mtd->oobavail);
|
|
+
|
|
+ if (mtd->ecc_strength) {
|
|
+ printf(" - ECC strength: %u bits\n", mtd->ecc_strength);
|
|
+ printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size);
|
|
+ printf(" - bitflip threshold: %u bits\n",
|
|
+ mtd->bitflip_threshold);
|
|
+ }
|
|
+
|
|
+ printf(" - 0x%012llx-0x%012llx : \"%s\"\n",
|
|
+ mtd->offset, mtd->offset + mtd->size, mtd->name);
|
|
+
|
|
+ /* MTD partitions, if any */
|
|
+ mtd_show_parts(mtd, 1);
|
|
+}
|
|
+
|
|
+static int do_nand_list(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd;
|
|
+ int dev_nb = 0;
|
|
+
|
|
+ /* Ensure all devices (and their partitions) are probed */
|
|
+ mtd_probe_devices();
|
|
+
|
|
+ printf("List of NAND devices:\n");
|
|
+ mtd_for_each_device(mtd) {
|
|
+ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH)
|
|
+ continue;
|
|
+
|
|
+ if (!mtd_is_partition(mtd))
|
|
+ mtd_show_device(mtd);
|
|
+
|
|
+ dev_nb++;
|
|
+ }
|
|
+
|
|
+ if (!dev_nb)
|
|
+ printf("No NAND MTD device found\n");
|
|
+
|
|
+ return CMD_RET_SUCCESS;
|
|
+}
|
|
+
|
|
+static struct mtd_info *nand_get_curr_dev(void)
|
|
+{
|
|
+ struct mtd_info *mtd, *first_dev = NULL;
|
|
+ int err, dev_nb = 0;
|
|
+
|
|
+ if (curr_dev) {
|
|
+ mtd = get_mtd_device(curr_dev, -1);
|
|
+ if (!IS_ERR_OR_NULL(mtd)) {
|
|
+ __put_mtd_device(mtd);
|
|
+ return mtd;
|
|
+ }
|
|
+
|
|
+ curr_dev = NULL;
|
|
+ }
|
|
+
|
|
+ /* Ensure all devices (and their partitions) are probed */
|
|
+ mtd_probe_devices();
|
|
+
|
|
+ mtd_for_each_device(mtd) {
|
|
+ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH)
|
|
+ continue;
|
|
+
|
|
+ if (!mtd_is_partition(mtd)) {
|
|
+ if (!first_dev)
|
|
+ first_dev = mtd;
|
|
+ dev_nb++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!dev_nb) {
|
|
+ printf("No NAND MTD device found\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (dev_nb > 1) {
|
|
+ printf("No active NAND MTD device specified\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ err = __get_mtd_device(first_dev);
|
|
+ if (err) {
|
|
+ printf("Failed to get MTD device '%s': err %d\n",
|
|
+ first_dev->name, err);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ curr_dev = first_dev;
|
|
+
|
|
+ printf("'%s' is now active device\n", first_dev->name);
|
|
+
|
|
+ return curr_dev;
|
|
+}
|
|
+
|
|
+static struct mtd_info *nand_get_part(struct mtd_info *master,
|
|
+ const char *name)
|
|
+{
|
|
+ struct mtd_info *slave;
|
|
+
|
|
+ list_for_each_entry(slave, &master->partitions, node) {
|
|
+ if (!strcmp(slave->name, name))
|
|
+ return slave;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int do_nand_info(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev();
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ mtd_show_device(mtd);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int do_nand_select(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd, *old;
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("MTD device name must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ mtd = get_mtd_device_nm(argv[1]);
|
|
+ if (!mtd) {
|
|
+ printf("MTD device '%s' not found\n", argv[1]);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (mtd_is_partition(mtd)) {
|
|
+ printf("Error: '%s' is a MTD partition\n", argv[1]);
|
|
+ __put_mtd_device(mtd);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) {
|
|
+ printf("Error: '%s' is not a NAND device\n", argv[1]);
|
|
+ __put_mtd_device(mtd);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (mtd == curr_dev) {
|
|
+ __put_mtd_device(mtd);
|
|
+ return CMD_RET_SUCCESS;
|
|
+ }
|
|
+
|
|
+ if (curr_dev) {
|
|
+ old = get_mtd_device(curr_dev, -1);
|
|
+ if (!IS_ERR_OR_NULL(old)) {
|
|
+ __put_mtd_device(old);
|
|
+ __put_mtd_device(curr_dev);
|
|
+ }
|
|
+
|
|
+ curr_dev = NULL;
|
|
+ }
|
|
+
|
|
+ curr_dev = mtd;
|
|
+
|
|
+ printf("'%s' is now active device\n", curr_dev->name);
|
|
+
|
|
+ return CMD_RET_SUCCESS;
|
|
+}
|
|
+
|
|
+static void dump_buf(const u8 *data, size_t size, u64 addr)
|
|
+{
|
|
+ const u8 *p = data;
|
|
+ u32 i, chklen;
|
|
+
|
|
+ while (size) {
|
|
+ chklen = 16;
|
|
+ if (chklen > size)
|
|
+ chklen = (u32)size;
|
|
+
|
|
+ printf("%08llx: ", addr);
|
|
+
|
|
+ for (i = 0; i < chklen; i++) {
|
|
+ if (i && (i % 4 == 0))
|
|
+ printf(" ");
|
|
+
|
|
+ printf("%02x ", p[i]);
|
|
+ }
|
|
+
|
|
+ for (i = chklen; i < 16; i++) {
|
|
+ if (i && (i % 4 == 0))
|
|
+ printf(" ");
|
|
+
|
|
+ printf(" ");
|
|
+ }
|
|
+ printf(" ");
|
|
+
|
|
+ for (i = 0; i < chklen; i++) {
|
|
+ if (p[i] < 32 || p[i] >= 0x7f)
|
|
+ printf(".");
|
|
+ else
|
|
+ printf("%c", p[i]);
|
|
+ }
|
|
+ printf("\n");
|
|
+
|
|
+ p += chklen;
|
|
+ size -= chklen;
|
|
+ addr += chklen;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int do_nand_dump(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev();
|
|
+ struct mtd_oob_ops io_op = {};
|
|
+ bool raw = false;
|
|
+ int ret;
|
|
+ u64 off;
|
|
+ u8 *buf;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (strstr(argv[0], ".raw"))
|
|
+ raw = true;
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Dump offset must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ off = simple_strtoull(argv[1], NULL, 0);
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off &= ~(u64)mtd->writesize_mask;
|
|
+
|
|
+ buf = malloc(mtd->writesize + mtd->oobsize);
|
|
+ if (!buf) {
|
|
+ printf("Failed to allocate buffer\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB;
|
|
+ io_op.len = mtd->writesize;
|
|
+ io_op.datbuf = buf;
|
|
+ io_op.ooblen = mtd->oobsize;
|
|
+ io_op.oobbuf = buf + mtd->writesize;
|
|
+
|
|
+ ret = mtd_read_oob(mtd, off, &io_op);
|
|
+ if (ret < 0 && ret != -EUCLEAN && ret != -EBADMSG) {
|
|
+ printf("Failed to read page at 0x%llx, err %d\n", off, ret);
|
|
+ free(buf);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ printf("Dump of %spage at 0x%llx:\n", raw ? "raw " : "", off);
|
|
+ dump_buf(buf, mtd->writesize, off);
|
|
+
|
|
+ printf("\n");
|
|
+ printf("OOB:\n");
|
|
+ dump_buf(buf + mtd->writesize, mtd->oobsize, 0);
|
|
+
|
|
+ free(buf);
|
|
+
|
|
+ return CMD_RET_SUCCESS;
|
|
+}
|
|
+
|
|
+static int do_nand_bad(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev();
|
|
+ u64 off = 0;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ while (off < mtd->size) {
|
|
+ if (mtd_block_isbad(mtd, off))
|
|
+ printf("\t%08llx\n", off);
|
|
+
|
|
+ off += mtd->erasesize;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int do_nand_markbad(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev();
|
|
+ u64 off;
|
|
+ int ret;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Missing address within a block to be marked bad\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ off = simple_strtoull(argv[1], NULL, 0);
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off &= ~(u64)mtd->erasesize_mask;
|
|
+
|
|
+ ret = mtd_block_markbad(mtd, off);
|
|
+
|
|
+ if (!ret)
|
|
+ printf("Block at 0x%08llx has been marked bad\n", off);
|
|
+ else
|
|
+ printf("Failed to mark bad block at 0x%08llx\n", off);
|
|
+
|
|
+ return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
|
|
+}
|
|
+
|
|
+static int do_nand_bitflip(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev();
|
|
+ struct mtd_oob_ops io_op = {};
|
|
+ u32 col, bit;
|
|
+ bool res;
|
|
+ u64 off;
|
|
+ u8 *buf;
|
|
+ int ret;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Missing address to generate bitflip\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ off = simple_strtoull(argv[1], NULL, 0);
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 3) {
|
|
+ printf("Missing column address\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ col = simple_strtoul(argv[2], NULL, 0);
|
|
+ if (col >= mtd->writesize + mtd->oobsize) {
|
|
+ printf("Column address must be less than %u\n",
|
|
+ mtd->writesize + mtd->oobsize);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 4) {
|
|
+ printf("Missing bit position\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ bit = simple_strtoul(argv[3], NULL, 0);
|
|
+ if (bit > 7) {
|
|
+ printf("Bit position must be less than 8\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off &= ~(u64)mtd->writesize_mask;
|
|
+
|
|
+ buf = malloc(mtd->writesize + mtd->oobsize);
|
|
+ if (!buf) {
|
|
+ printf("Failed to allocate buffer\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ io_op.mode = MTD_OPS_RAW;
|
|
+ io_op.len = mtd->writesize;
|
|
+ io_op.datbuf = buf;
|
|
+ io_op.ooblen = mtd->oobsize;
|
|
+ io_op.oobbuf = buf + mtd->writesize;
|
|
+
|
|
+ ret = mtd_read_oob(mtd, off, &io_op);
|
|
+ if (ret < 0) {
|
|
+ printf("Failed to read page at 0x%llx, err %d\n", off, ret);
|
|
+ free(buf);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (!(buf[col] & (1 << bit))) {
|
|
+ printf("Bit %u at byte %u is already zero\n", bit, col);
|
|
+ free(buf);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ buf[col] &= ~(1 << bit);
|
|
+
|
|
+ memset(&io_op, 0, sizeof(io_op));
|
|
+ io_op.mode = MTD_OPS_RAW;
|
|
+ io_op.len = mtd->writesize;
|
|
+ io_op.datbuf = buf;
|
|
+ io_op.ooblen = mtd->oobsize;
|
|
+ io_op.oobbuf = buf + mtd->writesize;
|
|
+
|
|
+ ret = mtd_write_oob(mtd, off, &io_op);
|
|
+
|
|
+ if (ret < 0) {
|
|
+ printf("Failed to write page at 0x%llx, err %d\n", off, ret);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ memset(&io_op, 0, sizeof(io_op));
|
|
+ io_op.mode = MTD_OPS_RAW;
|
|
+ io_op.len = mtd->writesize;
|
|
+ io_op.datbuf = buf;
|
|
+ io_op.ooblen = mtd->oobsize;
|
|
+ io_op.oobbuf = buf + mtd->writesize;
|
|
+
|
|
+ ret = mtd_read_oob(mtd, off, &io_op);
|
|
+ if (ret < 0) {
|
|
+ printf("Failed to read page at 0x%llx, err %d\n", off, ret);
|
|
+ free(buf);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ res = (buf[col] & (1 << bit)) == 0;
|
|
+ free(buf);
|
|
+
|
|
+ if (res) {
|
|
+ printf("Bit %u at byte %u has been changed to 0\n", bit, col);
|
|
+ return CMD_RET_SUCCESS;
|
|
+ }
|
|
+
|
|
+ printf("Failed to change bit %u at byte %u to 0\n", bit, col);
|
|
+ return CMD_RET_FAILURE;
|
|
+}
|
|
+
|
|
+static int do_nand_erase(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev(), *part;
|
|
+ bool spread = false, force = false;
|
|
+ u64 off, size, end, limit;
|
|
+ struct erase_info ei;
|
|
+ char *ends;
|
|
+ int ret;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (strstr(argv[0], ".spread"))
|
|
+ spread = true;
|
|
+
|
|
+ if (strstr(argv[0], ".force"))
|
|
+ force = true;
|
|
+
|
|
+ if (spread && force) {
|
|
+ printf("spread and force must not be set at the same time\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Erase start offset/partition must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ part = nand_get_part(mtd, argv[1]);
|
|
+ if (part) {
|
|
+ off = part->offset;
|
|
+
|
|
+ if (argc < 3)
|
|
+ size = part->size;
|
|
+ else
|
|
+ size = simple_strtoull(argv[2], NULL, 0);
|
|
+
|
|
+ if (size > part->size) {
|
|
+ printf("Erase end offset is larger than partition size\n");
|
|
+ printf("Erase size reduced to 0x%llx\n", part->size);
|
|
+
|
|
+ size = part->size;
|
|
+ }
|
|
+
|
|
+ limit = off + part->size;
|
|
+ } else {
|
|
+ off = simple_strtoull(argv[1], &ends, 0);
|
|
+
|
|
+ if (ends == argv[1] || *ends) {
|
|
+ printf("Partition '%s' not found\n", argv[1]);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 3) {
|
|
+ printf("Erase size offset must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ size = simple_strtoull(argv[2], NULL, 0);
|
|
+
|
|
+ if (off + size > mtd->size) {
|
|
+ printf("Erase end offset is larger than flash size\n");
|
|
+
|
|
+ size = mtd->size - off;
|
|
+ printf("Erase size reduced to 0x%llx\n", size);
|
|
+ }
|
|
+
|
|
+ limit = mtd->size;
|
|
+ }
|
|
+
|
|
+ end = off + size;
|
|
+ off &= ~(u64)mtd->erasesize_mask;
|
|
+ end = (end + mtd->erasesize_mask) & (~(u64)mtd->erasesize_mask);
|
|
+ size = end - off;
|
|
+
|
|
+ printf("Erasing from 0x%llx to 0x%llx, size 0x%llx ...\n",
|
|
+ off, end - 1, end - off);
|
|
+
|
|
+ while (size && off < limit) {
|
|
+ if (mtd_block_isbad(mtd, off)) {
|
|
+ printf("Bad block at 0x%llx", off);
|
|
+
|
|
+ if (spread) {
|
|
+ printf(" ... skipped\n");
|
|
+ off += mtd->erasesize;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!force) {
|
|
+ printf(" ... aborted\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ printf(" ... will be force erased\n");
|
|
+ }
|
|
+
|
|
+ memset(&ei, 0, sizeof(ei));
|
|
+
|
|
+ ei.mtd = mtd;
|
|
+ ei.addr = off;
|
|
+ ei.len = mtd->erasesize;
|
|
+ ei.scrub = force;
|
|
+
|
|
+ ret = mtd_erase(mtd, &ei);
|
|
+ if (ret) {
|
|
+ printf("Erase failed at 0x%llx\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off += mtd->erasesize;
|
|
+ size -= mtd->erasesize;
|
|
+ }
|
|
+
|
|
+ printf("Succeeded\n");
|
|
+
|
|
+ return CMD_RET_SUCCESS;
|
|
+}
|
|
+
|
|
+static bool is_empty_page(const u8 *buf, size_t size)
|
|
+{
|
|
+ size_t i;
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ if (buf[i] != 0xff)
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static int do_nand_io_normal(int argc, char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev(), *part;
|
|
+ bool spread = false, force = false, raw = false, writeff = false;
|
|
+ bool read = false, checkbad = true;
|
|
+ struct mtd_oob_ops io_op = {};
|
|
+ size_t size, padding, chksz;
|
|
+ uintptr_t addr;
|
|
+ u64 off, offp;
|
|
+ char *ends;
|
|
+ u8 *buf;
|
|
+ int ret;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (!strncmp(argv[0], "read", 4))
|
|
+ read = true;
|
|
+
|
|
+ if (strstr(argv[0], ".spread"))
|
|
+ spread = true;
|
|
+
|
|
+ if (strstr(argv[0], ".force"))
|
|
+ force = true;
|
|
+
|
|
+ if (strstr(argv[0], ".raw"))
|
|
+ raw = true;
|
|
+
|
|
+ if (strstr(argv[0], ".ff"))
|
|
+ writeff = true;
|
|
+
|
|
+ if (spread && force) {
|
|
+ printf("spread and force must not be set at the same time\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Data address must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ addr = simple_strtoul(argv[1], NULL, 0);
|
|
+
|
|
+ if (argc < 3) {
|
|
+ printf("Flash address/partition must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ part = nand_get_part(mtd, argv[2]);
|
|
+ if (part) {
|
|
+ if (argc < 4) {
|
|
+ off = 0;
|
|
+ } else {
|
|
+ off = simple_strtoull(argv[3], NULL, 0);
|
|
+ if (off + part->offset >= part->size) {
|
|
+ printf("Offset is larger than partition size\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (argc < 5) {
|
|
+ size = part->size - off;
|
|
+ } else {
|
|
+ size = simple_strtoul(argv[4], NULL, 0);
|
|
+ if (off + size > part->size) {
|
|
+ printf("Data size is too large\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ off += part->offset;
|
|
+ } else {
|
|
+ off = simple_strtoull(argv[2], &ends, 0);
|
|
+
|
|
+ if (ends == argv[1] || *ends) {
|
|
+ printf("Partition '%s' not found\n", argv[2]);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 4) {
|
|
+ printf("Data size must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ size = simple_strtoul(argv[3], NULL, 0);
|
|
+ if (off + size > mtd->size) {
|
|
+ printf("Data size is too large\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ buf = malloc(mtd->writesize);
|
|
+ if (!buf) {
|
|
+ printf("Failed to allocate buffer\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ printf("%s from 0x%llx to 0x%llx, size 0x%zx ...\n",
|
|
+ read ? "Reading" : "Writing", off, off + size - 1, size);
|
|
+
|
|
+ while (size && off < mtd->size) {
|
|
+ if (checkbad || !(off & mtd->erasesize_mask)) {
|
|
+ offp = off & ~(u64)mtd->erasesize_mask;
|
|
+
|
|
+ if (mtd_block_isbad(mtd, offp)) {
|
|
+ printf("Bad block at 0x%llx", offp);
|
|
+
|
|
+ if (spread) {
|
|
+ printf(" ... skipped\n");
|
|
+ off += mtd->erasesize;
|
|
+ checkbad = true;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!force) {
|
|
+ printf(" ... aborted\n");
|
|
+ goto err_out;
|
|
+ }
|
|
+
|
|
+ printf(" ... continue\n");
|
|
+ }
|
|
+
|
|
+ checkbad = false;
|
|
+ }
|
|
+
|
|
+ padding = off & mtd->writesize_mask;
|
|
+ chksz = mtd->writesize - padding;
|
|
+ chksz = min_t(size_t, chksz, size);
|
|
+
|
|
+ offp = off & ~(u64)mtd->writesize_mask;
|
|
+
|
|
+ memset(&io_op, 0, sizeof(io_op));
|
|
+ io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB;
|
|
+ io_op.len = mtd->writesize;
|
|
+
|
|
+ if (chksz < mtd->writesize)
|
|
+ io_op.datbuf = buf;
|
|
+ else
|
|
+ io_op.datbuf = (void *)addr;
|
|
+
|
|
+ if (read) {
|
|
+ ret = mtd_read_oob(mtd, offp, &io_op);
|
|
+ if (ret && ret != -EUCLEAN && ret != -EBADMSG)
|
|
+ goto io_err;
|
|
+
|
|
+ if (chksz < mtd->writesize)
|
|
+ memcpy((void *)addr, buf + padding, chksz);
|
|
+ } else {
|
|
+ if (chksz < mtd->writesize) {
|
|
+ memset(buf, 0xff, mtd->writesize);
|
|
+ memcpy(buf + padding, (void *)addr, chksz);
|
|
+ }
|
|
+
|
|
+ if (is_empty_page(io_op.datbuf, io_op.len) && !writeff)
|
|
+ ret = 0;
|
|
+ else
|
|
+ ret = mtd_write_oob(mtd, offp, &io_op);
|
|
+
|
|
+ if (ret)
|
|
+ goto io_err;
|
|
+ }
|
|
+
|
|
+ size -= chksz;
|
|
+ addr += chksz;
|
|
+ off += chksz;
|
|
+ }
|
|
+
|
|
+ if (!size) {
|
|
+ printf("Succeeded\n");
|
|
+ ret = CMD_RET_SUCCESS;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ printf("0x%zx byte%s remained for %s\n", size, size > 1 ? "s" : "",
|
|
+ read ? "read" : "write");
|
|
+ goto err_out;
|
|
+
|
|
+io_err:
|
|
+ printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, offp);
|
|
+
|
|
+err_out:
|
|
+ ret = CMD_RET_FAILURE;
|
|
+
|
|
+out:
|
|
+ free(buf);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int do_nand_io_page(int argc, char *const argv[])
|
|
+{
|
|
+ struct mtd_info *mtd = nand_get_curr_dev(), *part;
|
|
+ bool spread = false, force = false, raw = false, autooob = false;
|
|
+ bool read = false, checkbad = true, writeff = false;
|
|
+ struct mtd_oob_ops io_op = {};
|
|
+ uintptr_t addr;
|
|
+ u64 off, offp;
|
|
+ char *ends;
|
|
+ u32 count;
|
|
+ int ret;
|
|
+
|
|
+ if (!mtd)
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+ if (!strncmp(argv[0], "read", 4))
|
|
+ read = true;
|
|
+
|
|
+ if (strstr(argv[0], ".spread"))
|
|
+ spread = true;
|
|
+
|
|
+ if (strstr(argv[0], ".force"))
|
|
+ force = true;
|
|
+
|
|
+ if (strstr(argv[0], ".raw"))
|
|
+ raw = true;
|
|
+
|
|
+ if (strstr(argv[0], ".auto"))
|
|
+ autooob = true;
|
|
+
|
|
+ if (spread && force) {
|
|
+ printf("spread and force must not be set at the same time\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (raw && autooob) {
|
|
+ printf("raw and auto must not be set at the same time\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (argc < 2) {
|
|
+ printf("Data address must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ addr = simple_strtoul(argv[1], NULL, 0);
|
|
+
|
|
+ if (argc < 3) {
|
|
+ printf("Flash address/partition must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ part = nand_get_part(mtd, argv[2]);
|
|
+ if (part) {
|
|
+ if (argc < 4) {
|
|
+ printf("Partition offset / page count must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ if (argc < 5) {
|
|
+ off = 0;
|
|
+
|
|
+ count = simple_strtoul(argv[3], NULL, 0);
|
|
+ if (part->offset + count * mtd->writesize > part->size) {
|
|
+ printf("Page count exceeds partition size\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ } else {
|
|
+ off = simple_strtoull(argv[3], NULL, 0);
|
|
+ if (off >= part->size) {
|
|
+ printf("Offset 0x%llx is larger than partition size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off &= ~(u64)mtd->writesize_mask;
|
|
+
|
|
+ count = simple_strtoul(argv[4], NULL, 0);
|
|
+ if (part->offset + off + count * mtd->writesize > part->size) {
|
|
+ printf("Page count exceeds partition size\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ off += part->offset;
|
|
+ } else {
|
|
+ off = simple_strtoull(argv[2], &ends, 0);
|
|
+
|
|
+ if (ends == argv[1] || *ends) {
|
|
+ printf("Partition '%s' not found\n", argv[2]);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ if (off >= mtd->size) {
|
|
+ printf("Offset 0x%llx is larger than flash size\n", off);
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ off &= ~(u64)mtd->writesize_mask;
|
|
+
|
|
+ if (argc < 4) {
|
|
+ printf("Page count must be specified\n");
|
|
+ return CMD_RET_USAGE;
|
|
+ }
|
|
+
|
|
+ count = simple_strtoul(argv[3], NULL, 0);
|
|
+ if (off + count * mtd->writesize > mtd->size) {
|
|
+ printf("Page count exceeds flash size\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ printf("%s from 0x%llx to 0x%llx (+%u), count %u ...\n",
|
|
+ read ? "Reading" : "Writing", off,
|
|
+ off + count * mtd->writesize - 1, mtd->oobsize, count);
|
|
+
|
|
+ while (count && off < mtd->size) {
|
|
+ if (checkbad || !(off & mtd->erasesize_mask)) {
|
|
+ offp = off & ~(u64)mtd->erasesize_mask;
|
|
+
|
|
+ if (mtd_block_isbad(mtd, offp)) {
|
|
+ printf("Bad block at 0x%llx", offp);
|
|
+
|
|
+ if (spread) {
|
|
+ printf(" ... skipped\n");
|
|
+ off += mtd->erasesize;
|
|
+ checkbad = true;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!force) {
|
|
+ printf(" ... aborted\n");
|
|
+ return CMD_RET_FAILURE;
|
|
+ }
|
|
+
|
|
+ printf(" ... continue\n");
|
|
+ }
|
|
+
|
|
+ checkbad = false;
|
|
+ }
|
|
+
|
|
+ memset(&io_op, 0, sizeof(io_op));
|
|
+
|
|
+ if (raw)
|
|
+ io_op.mode = MTD_OPS_RAW;
|
|
+ else if (autooob)
|
|
+ io_op.mode = MTD_OPS_AUTO_OOB;
|
|
+ else
|
|
+ io_op.mode = MTD_OPS_PLACE_OOB;
|
|
+
|
|
+ io_op.len = mtd->writesize;
|
|
+ io_op.ooblen = mtd->oobsize;
|
|
+ io_op.datbuf = (void *)addr;
|
|
+ io_op.oobbuf = io_op.datbuf + mtd->writesize;
|
|
+
|
|
+ if (read) {
|
|
+ ret = mtd_read_oob(mtd, off, &io_op);
|
|
+ if (ret && ret != -EUCLEAN && ret != -EBADMSG)
|
|
+ goto io_err;
|
|
+ } else {
|
|
+ if (is_empty_page((void *)addr, mtd->writesize + mtd->oobsize) && !writeff)
|
|
+ ret = 0;
|
|
+ else
|
|
+ ret = mtd_write_oob(mtd, off, &io_op);
|
|
+
|
|
+ if (ret)
|
|
+ goto io_err;
|
|
+ }
|
|
+
|
|
+ count--;
|
|
+ addr += mtd->writesize + mtd->oobsize;
|
|
+ off += mtd->writesize;
|
|
+ }
|
|
+
|
|
+ if (!count) {
|
|
+ printf("Succeeded\n");
|
|
+ return CMD_RET_SUCCESS;
|
|
+ }
|
|
+
|
|
+ printf("%u page%s remained for %s\n", count, count > 1 ? "s" : "",
|
|
+ read ? "read" : "write");
|
|
+ return CMD_RET_FAILURE;
|
|
+
|
|
+io_err:
|
|
+ printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, off);
|
|
+ return CMD_RET_FAILURE;
|
|
+}
|
|
+
|
|
+static int do_nand_io(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
+ char *const argv[])
|
|
+{
|
|
+ if (strstr(argv[0], ".oob"))
|
|
+ return do_nand_io_page(argc, argv);
|
|
+
|
|
+ return do_nand_io_normal(argc, argv);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_SYS_LONGHELP
|
|
+static char nand_help_text[] =
|
|
+ "- NAND flash R/W and debugging utility\n"
|
|
+ "nand list\n"
|
|
+ "nand info - Show active NAND devices\n"
|
|
+ "nand select <name> - Select active NAND devices\n"
|
|
+ "nand dump[.raw] <off>\n"
|
|
+ "nand bad\n"
|
|
+ "nand markbad <off>\n"
|
|
+ "nand bitflip <off> <col> <bit>\n"
|
|
+ "nand erase[.spread|.force] [<off> <size>|<part> [<size>]]\n"
|
|
+ "nand read[.spread|.force][.raw] <addr> <off> <size>\n"
|
|
+ " <addr> <part> [<off> [<size>]]\n"
|
|
+ "nand write[.spread|.force][.raw][.ff] <addr> <off> <size>\n"
|
|
+ " <addr> <part> [<off> [<size>]]\n"
|
|
+ "nand read.oob[.spread|.force][.raw|.auto] <addr> <off> <count>\n"
|
|
+ " <addr> <part> [<off>] <count>\n"
|
|
+ "nand write.oob[.spread|.force][.raw|.auto][.ff] <addr> <off> <count>\n"
|
|
+ " <addr> <part> [<off>] <count>\n";
|
|
+#endif
|
|
+
|
|
+U_BOOT_CMD_WITH_SUBCMDS(nand, "NAND utility",
|
|
+ nand_help_text,
|
|
+ U_BOOT_SUBCMD_MKENT(list, 1, 0, do_nand_list),
|
|
+ U_BOOT_SUBCMD_MKENT(info, 1, 0, do_nand_info),
|
|
+ U_BOOT_SUBCMD_MKENT(select, 2, 0, do_nand_select),
|
|
+ U_BOOT_SUBCMD_MKENT(dump, 2, 0, do_nand_dump),
|
|
+ U_BOOT_SUBCMD_MKENT(bad, 1, 0, do_nand_bad),
|
|
+ U_BOOT_SUBCMD_MKENT(markbad, 2, 0, do_nand_markbad),
|
|
+ U_BOOT_SUBCMD_MKENT(bitflip, 4, 0, do_nand_bitflip),
|
|
+ U_BOOT_SUBCMD_MKENT(erase, 3, 0, do_nand_erase),
|
|
+ U_BOOT_SUBCMD_MKENT(read, 5, 0, do_nand_io),
|
|
+ U_BOOT_SUBCMD_MKENT(write, 5, 0, do_nand_io)
|
|
+);
|