[U-Boot] [PATCH v7 10/13] cmd: mtd: add 'mtd' command

Stefan Roese sr at denx.de
Sat Sep 1 08:59:56 UTC 2018


On 31.08.2018 16:57, Miquel Raynal wrote:
> There should not be a 'nand' command, a 'sf' command and certainly not
> a new 'spi-nand' command. Write a 'mtd' command instead to manage all
> MTD devices/partitions at once. This should be the preferred way to
> access any MTD device.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> Acked-by: Jagan Teki <jagan at openedev.com>
> ---
>   cmd/Kconfig          |  10 +-
>   cmd/Makefile         |   1 +
>   cmd/mtd.c            | 517 +++++++++++++++++++++++++++++++++++++++++++
>   drivers/mtd/Makefile |   2 +-
>   include/mtd.h        |   1 +
>   5 files changed, 528 insertions(+), 3 deletions(-)
>   create mode 100644 cmd/mtd.c
> 
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index ef43ed8dda..0778d2ecff 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -847,6 +847,12 @@ config CMD_MMC_SWRITE
>   	  Enable support for the "mmc swrite" command to write Android sparse
>   	  images to eMMC.
>   
> +config CMD_MTD
> +	bool "mtd"
> +	select MTD_PARTITIONS
> +	help
> +	  MTD commands support.
> +
>   config CMD_NAND
>   	bool "nand"
>   	default y if NAND_SUNXI
> @@ -1671,14 +1677,14 @@ config CMD_MTDPARTS
>   
>   config MTDIDS_DEFAULT
>   	string "Default MTD IDs"
> -	depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH
> +	depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH
>   	help
>   	  Defines a default MTD IDs list for use with MTD partitions in the
>   	  Linux MTD command line partitions format.
>   
>   config MTDPARTS_DEFAULT
>   	string "Default MTD partition scheme"
> -	depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH
> +	depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH
>   	help
>   	  Defines a default MTD partitioning scheme in the Linux MTD command
>   	  line partitions format
> diff --git a/cmd/Makefile b/cmd/Makefile
> index 323f1fd2c7..32fd102189 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -90,6 +90,7 @@ obj-$(CONFIG_CMD_MISC) += misc.o
>   obj-$(CONFIG_CMD_MMC) += mmc.o
>   obj-$(CONFIG_CMD_MMC_SPI) += mmc_spi.o
>   obj-$(CONFIG_MP) += mp.o
> +obj-$(CONFIG_CMD_MTD) += mtd.o
>   obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o
>   obj-$(CONFIG_CMD_NAND) += nand.o
>   obj-$(CONFIG_CMD_NET) += net.o
> diff --git a/cmd/mtd.c b/cmd/mtd.c
> new file mode 100644
> index 0000000000..32295fe86c
> --- /dev/null
> +++ b/cmd/mtd.c
> @@ -0,0 +1,517 @@
> +// SPDX-License-Identifier:  GPL-2.0+
> +/*
> + * mtd.c
> + *
> + * Generic command to handle basic operations on any memory device.
> + *
> + * Copyright: Bootlin, 2018
> + * Author: Miquèl Raynal <miquel.raynal at bootlin.com>
> + */
> +
> +#include <command.h>
> +#include <common.h>
> +#include <console.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/partitions.h>
> +#include <malloc.h>
> +#include <mapmem.h>
> +#include <mtd.h>
> +#include <dm/device.h>
> +#include <dm/uclass-internal.h>
> +
> +#define MTD_NAME_MAX_LEN 20
> +
> +static char *old_mtdparts;
> +
> +static void mtd_dump_buf(u8 *buf, uint len, uint offset)
> +{
> +	int i, j;
> +
> +	for (i = 0; i < len; ) {
> +		printf("0x%08x:\t", offset + i);
> +		for (j = 0; j < 8; j++)
> +			printf("%02x ", buf[i + j]);
> +		printf(" ");
> +		i += 8;
> +		for (j = 0; j < 8; j++)
> +			printf("%02x ", buf[i + j]);
> +		printf("\n");
> +		i += 8;
> +	}
> +}
> +
> +static void mtd_show_parts(struct mtd_info *mtd, int level)
> +{
> +	struct mtd_info *part;
> +	int i;
> +
> +	if (list_empty(&mtd->partitions))
> +		return;
> +
> +	list_for_each_entry(part, &mtd->partitions, node) {
> +		for (i = 0; i < level; i++)
> +			printf("\t");
> +		printf("* %s\n", part->name);
> +		for (i = 0; i < level; i++)
> +			printf("\t");
> +		printf("  > Offset: 0x%llx bytes\n", part->offset);
> +		for (i = 0; i < level; i++)
> +			printf("\t");
> +		printf("  > Size: 0x%llx bytes\n", part->size);
> +
> +		mtd_show_parts(part, level + 1);
> +	}
> +}
> +
> +static void mtd_show_device(struct mtd_info *mtd)
> +{
> +	/* Device */
> +	printf("* %s", mtd->name);
> +	if (mtd->dev)
> +		printf(" [device: %s] [parent: %s] [driver: %s]",
> +		       mtd->dev->name, mtd->dev->parent->name,
> +		       mtd->dev->driver->name);
> +	printf("\n");
> +
> +	/* MTD device information */
> +	printf("  > type: ");
> +	switch (mtd->type) {
> +	case MTD_RAM:
> +		printf("RAM\n");
> +		break;
> +	case MTD_ROM:
> +		printf("ROM\n");
> +		break;
> +	case MTD_NORFLASH:
> +		printf("NOR flash\n");
> +		break;
> +	case MTD_NANDFLASH:
> +		printf("NAND flash\n");
> +		break;
> +	case MTD_DATAFLASH:
> +		printf("Data flash\n");
> +		break;
> +	case MTD_UBIVOLUME:
> +		printf("UBI volume\n");
> +		break;
> +	case MTD_MLCNANDFLASH:
> +		printf("MLC NAND flash\n");
> +		break;
> +	case MTD_ABSENT:
> +	default:
> +		printf("Unknown\n");
> +		break;
> +	}
> +
> +	printf("  > Size: 0x%llx bytes\n", mtd->size);
> +	printf("  > Block: 0x%x bytes\n", mtd->erasesize);
> +	printf("  > Min I/O: 0x%x bytes\n", mtd->writesize);
> +
> +	if (mtd->oobsize) {
> +		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);
> +	}
> +
> +	/* MTD partitions, if any */
> +	mtd_show_parts(mtd, 1);
> +}
> +
> +#if IS_ENABLED(CONFIG_MTD)
> +static void mtd_probe_uclass_mtd_devs(void)
> +{
> +	struct udevice *dev;
> +	int idx = 0;
> +
> +	/* Probe devices with DM compliant drivers */
> +	while (!uclass_find_device(UCLASS_MTD, idx, &dev) && dev) {
> +		mtd_probe(dev);
> +		idx++;
> +	}
> +}
> +#else
> +static void mtd_probe_uclass_mtd_devs(void) { }
> +#endif

Does it make sense to support this new command without CONFIG_MTD
enabled? Do you have a use case for this?

> +
> +int mtd_probe_devices(void)
> +{
> +	const char *mtdparts = env_get("mtdparts");
> +	bool remaining_partitions = true;
> +	struct mtd_info *mtd;
> +	int i;
> +
> +	mtd_probe_uclass_mtd_devs();
> +
> +	/* Check if mtdparts changed since last call, if not, just exit */
> +	if (!strcmp(mtdparts, old_mtdparts))
> +		return CMD_RET_SUCCESS;
> +
> +	/* Update the local copy of mtdparts */
> +	free(old_mtdparts);
> +	old_mtdparts = strdup(mtdparts);
> +
> +	/* If at least one partition is still in use, do not delete anything */
> +	mtd_for_each_device(mtd) {
> +		if (mtd->usecount) {
> +			printf("Partition \"%s\" already in use, aborting\n",
> +			       mtd->name);
> +			return CMD_RET_FAILURE;
> +		}
> +	}
> +
> +	/*
> +	 * Everything looks clear, remove all partitions. It is not safe to
> +	 * remove entries from the mtd_for_each_device loop as it uses idr
> +	 * indexes and the partitions removal is done in bulk (all partitions of
> +	 * one device at the same time), so break and iterate from start each
> +	 * time a new partition is found and deleted.
> +	 */
> +	while (remaining_partitions) {
> +		remaining_partitions = false;
> +		mtd_for_each_device(mtd) {
> +			if (!mtd_is_partition(mtd) && mtd_has_partitions(mtd)) {
> +				del_mtd_partitions(mtd);
> +				remaining_partitions = true;
> +				break;
> +			}
> +		}
> +	}
> +
> +	/* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */
> +	if (strstr(mtdparts, "mtdparts="))
> +		mtdparts += 9;
> +
> +	/* For each MTD device in mtdparts */
> +	while (mtdparts[0] != '\0') {
> +		char mtdname[MTD_NAME_MAX_LEN], *colon;
> +		struct mtd_partition *parts;
> +		int mtdname_len, len;
> +		int ret;
> +
> +		colon = strchr(mtdparts, ':');
> +		if (!colon) {
> +			printf("Wrong mtdparts: %s\n", mtdparts);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		mtdname_len = colon - mtdparts;
> +		strncpy(mtdname, mtdparts, mtdname_len);
> +		mtdname[mtdname_len] = '\0';
> +		/* Move the pointer forward (including the ':') */
> +		mtdparts += mtdname_len + 1;
> +		mtd = get_mtd_device_nm(mtdname);
> +		if (IS_ERR_OR_NULL(mtd)) {
> +			char altname[MTD_NAME_MAX_LEN];
> +
> +			/*
> +			 * The MTD device named "mtdname" does not exist. Try to
> +			 * find a correspondance with an MTD device having the
> +			 * same type and number as defined in the mtdids.
> +			 */
> +			printf("No device named %s\n", mtdname);
> +			ret = mtd_search_alternate_name(mtdname, altname,
> +							MTD_NAME_MAX_LEN);
> +			if (ret)  {
> +				printf("Did not found an equivalent in mtdids\n");
> +				return CMD_RET_FAILURE;
> +			}
> +
> +			mtd = get_mtd_device_nm(altname);
> +			if (IS_ERR_OR_NULL(mtd)) {
> +				printf("No device named %s\n", altname);
> +				return CMD_RET_FAILURE;
> +			}
> +		}
> +		put_mtd_device(mtd);
> +
> +		/*
> +		 * Parse the MTD device partitions. It will update the mtdparts
> +		 * pointer, create an array of parts (that must be freed), and
> +		 * return the number of partition structures in the array.
> +		 */
> +		ret = mtd_parse_partitions(mtd, &mtdparts, &parts, &len);
> +		if (ret) {
> +			printf("Could not parse device %s\n", mtd->name);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		if (!len)
> +			continue;
> +
> +		/*
> +		 * Create the new MTD partitions and free the structures
> +		 * allocated during the parsing.
> +		 */
> +		add_mtd_partitions(mtd, parts, len);
> +
> +		for (i = 0; i < len; i++)
> +			free(&parts[i].name);
> +		free(parts);
> +	}
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len)
> +{
> +	do_div(len, mtd->writesize);
> +
> +	return len;
> +}
> +
> +static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size)
> +{
> +	return do_div(size, mtd->writesize);
> +}
> +
> +static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
> +{
> +	return do_div(size, mtd->erasesize);
> +}
> +
> +static int do_mtd_list(void)
> +{
> +	struct mtd_info *mtd;
> +	int dev_nb = 0;
> +
> +#if IS_ENABLED(CONFIG_MTD)
> +	/* Ensure all devices (and their partitions) are probed */
> +	mtd_probe_devices();
> +#endif
> +
> +	printf("List of MTD devices:\n");
> +	mtd_for_each_device(mtd) {
> +		if (!mtd_is_partition(mtd))
> +			mtd_show_device(mtd);
> +
> +		dev_nb++;
> +	}
> +
> +	if (!dev_nb) {
> +		printf("No MTD device found\n");
> +		return CMD_RET_FAILURE;
> +	}
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
> +{
> +	struct mtd_info *mtd;
> +	const char *cmd;
> +	char *mtdname;
> +	int ret;
> +
> +	/* All MTD commands need at least two arguments */
> +	if (argc < 2)
> +		return CMD_RET_USAGE;
> +
> +	/* Parse the command name and its optional suffixes */
> +	cmd = argv[1];
> +
> +	/* List the MTD devices if that is what the user wants */
> +	if (strcmp(cmd, "list") == 0)
> +		return do_mtd_list();
> +
> +	/*
> +	 * The remaining commands require also at least a device ID.
> +	 * Check the selected device is valid. Ensure it is probed.
> +	 */
> +	if (argc < 3)
> +		return CMD_RET_USAGE;
> +
> +	mtdname = argv[2];
> +	mtd_probe_devices();
> +	mtd = get_mtd_device_nm(mtdname);
> +	if (IS_ERR_OR_NULL(mtd)) {
> +		printf("MTD device %s not found, ret %ld\n",
> +		       mtdname, PTR_ERR(mtd));
> +		return CMD_RET_FAILURE;
> +	}
> +	put_mtd_device(mtd);
> +
> +	argc -= 3;
> +	argv += 3;
> +
> +	/* Do the parsing */
> +	if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) ||
> +	    !strncmp(cmd, "write", 5)) {
> +		struct mtd_oob_ops io_op = {};
> +		bool dump, read, raw, woob;
> +		uint user_addr = 0;
> +		uint nb_pages;
> +		u8 *buf;
> +		u64 off, len, default_len;
> +		u32 oob_len;
> +
> +		dump = !strncmp(cmd, "dump", 4);
> +		read = dump || !strncmp(cmd, "read", 4);
> +		raw = strstr(cmd, ".raw");
> +		woob = strstr(cmd, ".oob");
> +
> +		if (!dump) {
> +			if (!argc)
> +				return CMD_RET_USAGE;
> +
> +			user_addr = simple_strtoul(argv[0], NULL, 16);
> +			argc--;
> +			argv++;
> +		}
> +
> +		default_len = dump ? mtd->writesize : mtd->size;
> +		off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
> +		len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) :
> +				 default_len;
> +		nb_pages = mtd_len_to_pages(mtd, len);
> +		oob_len = woob ? nb_pages * mtd->oobsize : 0;
> +
> +		if (mtd_is_aligned_with_min_io_size(mtd, off)) {
> +			printf("Offset not aligned with a page (0x%x)\n",
> +			       mtd->writesize);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		if (mtd_is_aligned_with_min_io_size(mtd, len)) {
> +			printf("Size not a multiple of a page (0x%x)\n",
> +			       mtd->writesize);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		if (dump)
> +			buf = kmalloc(len + oob_len, GFP_KERNEL);
> +		else
> +			buf = map_sysmem(user_addr, 0);
> +
> +		if (!buf) {
> +			printf("Could not map/allocate the user buffer\n");
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		printf("%s %lldB (%d page(s)) at offset 0x%08llx%s%s\n",
> +		       read ? "Reading" : "Writing", len, nb_pages, off,
> +		       raw ? " [raw]" : "", woob ? " [oob]" : "");
> +
> +		io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
> +		io_op.len = len;
> +		io_op.ooblen = oob_len;
> +		io_op.datbuf = buf;
> +		io_op.oobbuf = woob ? &buf[len] : NULL;
> +
> +		if (read)
> +			ret = mtd_read_oob(mtd, off, &io_op);
> +		else
> +			ret = mtd_write_oob(mtd, off, &io_op);
> +
> +		if (!ret && dump) {
> +			uint page;
> +
> +			for (page = 0; page < nb_pages; page++) {
> +				u64 data_off = page * mtd->writesize;
> +
> +				printf("\nDump %d data bytes from 0x%08llx:\n",
> +				       mtd->writesize, data_off);
> +				mtd_dump_buf(buf, mtd->writesize, data_off);
> +
> +				if (woob) {
> +					u64 oob_off = page * mtd->oobsize;
> +
> +					printf("Dump %d OOB bytes from page at 0x%08llx:\n",
> +					       mtd->oobsize, data_off);
> +					mtd_dump_buf(&buf[len + oob_off],
> +						     mtd->oobsize, 0);
> +				}
> +			}
> +		}
> +
> +		if (dump)
> +			kfree(buf);
> +		else
> +			unmap_sysmem(buf);
> +
> +		if (ret) {
> +			printf("%s on %s failed with error %d\n",
> +			       read ? "Read" : "Write", mtd->name, ret);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +	} else if (!strcmp(cmd, "erase")) {
> +		bool scrub = strstr(cmd, ".dontskipbad");
> +		struct erase_info erase_op = {};
> +		u64 off, len;
> +
> +		off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
> +		len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size;
> +
> +		if (mtd_is_aligned_with_block_size(mtd, off)) {
> +			printf("Offset not aligned with a block (0x%x)\n",
> +			       mtd->erasesize);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		if (mtd_is_aligned_with_block_size(mtd, len)) {
> +			printf("Size not a multiple of a block (0x%x)\n",
> +			       mtd->erasesize);
> +			return CMD_RET_FAILURE;
> +		}
> +
> +		printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n",
> +		       off, off + len - 1, mtd_div_by_eb(len, mtd));
> +
> +		erase_op.mtd = mtd;
> +		erase_op.addr = off;
> +		erase_op.len = len;
> +		erase_op.scrub = scrub;
> +
> +		while (erase_op.len) {
> +			ret = mtd_erase(mtd, &erase_op);
> +
> +			/* Abort if its not an bad block error */
> +			if (ret != -EIO)
> +				break;
> +
> +			printf("Skipping bad block at 0x%08llx\n",
> +			       erase_op.fail_addr);
> +
> +			/* Skip bad block and continue behind it */
> +			erase_op.len -= erase_op.fail_addr - erase_op.addr;
> +			erase_op.len -= mtd->erasesize;
> +			erase_op.addr = erase_op.fail_addr + mtd->erasesize;
> +		}
> +	} else {
> +		return CMD_RET_USAGE;
> +	}
> +
> +	if (ret)
> +		return CMD_RET_FAILURE;
> +
> +	return CMD_RET_SUCCESS;
> +}
> +
> +static char mtd_help_text[] =
> +#ifdef CONFIG_SYS_LONGHELP
> +	"- generic operations on memory technology devices\n\n"
> +	"mtd list\n"
> +	"mtd read[.raw][.oob]    <name> <addr> [<off> [<size>]]\n"
> +	"mtd dump[.raw][.oob]    <name>        [<off> [<size>]]\n"
> +	"mtd write[.raw][.oob]   <name> <addr> [<off> [<size>]]\n"
> +	"mtd erase[.dontskipbad] <name>        [<off> [<size>]]\n"
> +	"\n"
> +	"With:\n"
> +	"\t<name>: NAND partition/chip name\n"
> +	"\t<addr>: user address from/to which data will be retrieved/stored\n"
> +	"\t<off>: offset in <name> in bytes (default: start of the part)\n"
> +	"\t\t* must be block-aligned for erase\n"
> +	"\t\t* must be page-aligned otherwise\n"
> +	"\t<size>: length of the operation in bytes (default: the entire device)\n"
> +	"\t\t* must be a multiple of a block for erase\n"
> +	"\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n"
> +#endif
> +	"";
> +
> +U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text);
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index cdf515256a..22ceda93c0 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -3,7 +3,7 @@
>   # (C) Copyright 2000-2007
>   # Wolfgang Denk, DENX Software Engineering, wd at denx.de.
>   
> -ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)))
> +ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)$(CONFIG_CMD_MTD)))
>   obj-y += mtdcore.o mtd_uboot.o
>   endif
>   obj-$(CONFIG_MTD) += mtd-uclass.o
> diff --git a/include/mtd.h b/include/mtd.h
> index 6e6da3002f..04b1c50ac7 100644
> --- a/include/mtd.h
> +++ b/include/mtd.h
> @@ -20,5 +20,6 @@ static inline struct mtd_info *mtd_get_info(struct udevice *dev)
>   }
>   
>   int mtd_probe(struct udevice *dev);
> +int mtd_probe_devices(void);
>   
>   #endif	/* _MTD_H_ */
> 

Reviewed-by: Stefan Roese <sr at denx.de>

Thanks,
Stefan


More information about the U-Boot mailing list