cmd: mtd: add 'mtd' command
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@bootlin.com> Acked-by: Jagan Teki <jagan@openedev.com> Reviewed-by: Stefan Roese <sr@denx.de> Reviewed-by: Boris Brezillon <boris.brezillon@bootlin.com>
This commit is contained in:
		
							parent
							
								
									2a74930da5
								
							
						
					
					
						commit
						5db66b3aee
					
				
							
								
								
									
										10
									
								
								cmd/Kconfig
								
								
								
								
							
							
						
						
									
										10
									
								
								cmd/Kconfig
								
								
								
								
							|  | @ -864,6 +864,12 @@ config CMD_MMC_SWRITE | ||||||
| 	  Enable support for the "mmc swrite" command to write Android sparse | 	  Enable support for the "mmc swrite" command to write Android sparse | ||||||
| 	  images to eMMC. | 	  images to eMMC. | ||||||
| 
 | 
 | ||||||
|  | config CMD_MTD | ||||||
|  | 	bool "mtd" | ||||||
|  | 	select MTD_PARTITIONS | ||||||
|  | 	help | ||||||
|  | 	  MTD commands support. | ||||||
|  | 
 | ||||||
| config CMD_NAND | config CMD_NAND | ||||||
| 	bool "nand" | 	bool "nand" | ||||||
| 	default y if NAND_SUNXI | 	default y if NAND_SUNXI | ||||||
|  | @ -1697,14 +1703,14 @@ config CMD_MTDPARTS | ||||||
| 
 | 
 | ||||||
| config MTDIDS_DEFAULT | config MTDIDS_DEFAULT | ||||||
| 	string "Default MTD IDs" | 	string "Default MTD IDs" | ||||||
| 	depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH | 	depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH | ||||||
| 	help | 	help | ||||||
| 	  Defines a default MTD IDs list for use with MTD partitions in the | 	  Defines a default MTD IDs list for use with MTD partitions in the | ||||||
| 	  Linux MTD command line partitions format. | 	  Linux MTD command line partitions format. | ||||||
| 
 | 
 | ||||||
| config MTDPARTS_DEFAULT | config MTDPARTS_DEFAULT | ||||||
| 	string "Default MTD partition scheme" | 	string "Default MTD partition scheme" | ||||||
| 	depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH | 	depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH | ||||||
| 	help | 	help | ||||||
| 	  Defines a default MTD partitioning scheme in the Linux MTD command | 	  Defines a default MTD partitioning scheme in the Linux MTD command | ||||||
| 	  line partitions format | 	  line partitions format | ||||||
|  |  | ||||||
|  | @ -92,6 +92,7 @@ obj-$(CONFIG_CMD_MISC) += misc.o | ||||||
| obj-$(CONFIG_CMD_MMC) += mmc.o | obj-$(CONFIG_CMD_MMC) += mmc.o | ||||||
| obj-$(CONFIG_CMD_MMC_SPI) += mmc_spi.o | obj-$(CONFIG_CMD_MMC_SPI) += mmc_spi.o | ||||||
| obj-$(CONFIG_MP) += mp.o | obj-$(CONFIG_MP) += mp.o | ||||||
|  | obj-$(CONFIG_CMD_MTD) += mtd.o | ||||||
| obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o | obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o | ||||||
| obj-$(CONFIG_CMD_NAND) += nand.o | obj-$(CONFIG_CMD_NAND) += nand.o | ||||||
| obj-$(CONFIG_CMD_NET) += net.o | obj-$(CONFIG_CMD_NET) += net.o | ||||||
|  |  | ||||||
|  | @ -0,0 +1,473 @@ | ||||||
|  | // 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@bootlin.com> | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <command.h> | ||||||
|  | #include <common.h> | ||||||
|  | #include <console.h> | ||||||
|  | #include <malloc.h> | ||||||
|  | #include <mapmem.h> | ||||||
|  | #include <mtd.h> | ||||||
|  | 
 | ||||||
|  | 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 void mtd_dump_buf(const 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_dump_device_buf(struct mtd_info *mtd, u64 start_off, | ||||||
|  | 				const u8 *buf, u64 len, bool woob) | ||||||
|  | { | ||||||
|  | 	bool has_pages = mtd->type == MTD_NANDFLASH || | ||||||
|  | 		mtd->type == MTD_MLCNANDFLASH; | ||||||
|  | 	int npages = mtd_len_to_pages(mtd, len); | ||||||
|  | 	uint page; | ||||||
|  | 
 | ||||||
|  | 	if (has_pages) { | ||||||
|  | 		for (page = 0; page < npages; page++) { | ||||||
|  | 			u64 data_off = page * mtd->writesize; | ||||||
|  | 
 | ||||||
|  | 			printf("\nDump %d data bytes from 0x%08llx:\n", | ||||||
|  | 			       mtd->writesize, start_off + data_off); | ||||||
|  | 			mtd_dump_buf(&buf[data_off], | ||||||
|  | 				     mtd->writesize, start_off + data_off); | ||||||
|  | 
 | ||||||
|  | 			if (woob) { | ||||||
|  | 				u64 oob_off = page * mtd->oobsize; | ||||||
|  | 
 | ||||||
|  | 				printf("Dump %d OOB bytes from page at 0x%08llx:\n", | ||||||
|  | 				       mtd->oobsize, start_off + data_off); | ||||||
|  | 				mtd_dump_buf(&buf[len + oob_off], | ||||||
|  | 					     mtd->oobsize, 0); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		printf("\nDump %lld data bytes from 0x%llx:\n", | ||||||
|  | 		       len, start_off); | ||||||
|  | 		mtd_dump_buf(buf, len, start_off); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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_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("  - block size: 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); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	printf("  - 0x%012llx-0x%012llx : \"%s\"\n", | ||||||
|  | 	       mtd->offset, mtd->offset + mtd->size, mtd->name); | ||||||
|  | 
 | ||||||
|  | 	/* MTD partitions, if any */ | ||||||
|  | 	mtd_show_parts(mtd, 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Logic taken from fs/ubifs/recovery.c:is_empty() */ | ||||||
|  | static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < op->len; i++) | ||||||
|  | 		if (op->datbuf[i] != 0xff) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < op->ooblen; i++) | ||||||
|  | 		if (op->oobbuf[i] != 0xff) | ||||||
|  | 			return false; | ||||||
|  | 
 | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_mtd_list(void) | ||||||
|  | { | ||||||
|  | 	struct mtd_info *mtd; | ||||||
|  | 	int dev_nb = 0; | ||||||
|  | 
 | ||||||
|  | 	/* Ensure all devices (and their partitions) are probed */ | ||||||
|  | 	mtd_probe_devices(); | ||||||
|  | 
 | ||||||
|  | 	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 mtd_special_write_oob(struct mtd_info *mtd, u64 off, | ||||||
|  | 				 struct mtd_oob_ops *io_op, | ||||||
|  | 				 bool write_empty_pages, bool woob) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * By default, do not write an empty page. | ||||||
|  | 	 * Skip it by simulating a successful write. | ||||||
|  | 	 */ | ||||||
|  | 	if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { | ||||||
|  | 		io_op->retlen = mtd->writesize; | ||||||
|  | 		io_op->oobretlen = woob ? mtd->oobsize : 0; | ||||||
|  | 	} else { | ||||||
|  | 		ret = mtd_write_oob(mtd, off, io_op); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | ||||||
|  | { | ||||||
|  | 	struct mtd_info *mtd; | ||||||
|  | 	const char *cmd; | ||||||
|  | 	char *mtd_name; | ||||||
|  | 
 | ||||||
|  | 	/* 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; | ||||||
|  | 
 | ||||||
|  | 	mtd_name = argv[2]; | ||||||
|  | 	mtd_probe_devices(); | ||||||
|  | 	mtd = get_mtd_device_nm(mtd_name); | ||||||
|  | 	if (IS_ERR_OR_NULL(mtd)) { | ||||||
|  | 		printf("MTD device %s not found, ret %ld\n", | ||||||
|  | 		       mtd_name, 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)) { | ||||||
|  | 		bool has_pages = mtd->type == MTD_NANDFLASH || | ||||||
|  | 				 mtd->type == MTD_MLCNANDFLASH; | ||||||
|  | 		bool dump, read, raw, woob, write_empty_pages; | ||||||
|  | 		struct mtd_oob_ops io_op = {}; | ||||||
|  | 		uint user_addr = 0, npages; | ||||||
|  | 		u64 start_off, off, len, remaining, default_len; | ||||||
|  | 		u32 oob_len; | ||||||
|  | 		u8 *buf; | ||||||
|  | 		int ret; | ||||||
|  | 
 | ||||||
|  | 		dump = !strncmp(cmd, "dump", 4); | ||||||
|  | 		read = dump || !strncmp(cmd, "read", 4); | ||||||
|  | 		raw = strstr(cmd, ".raw"); | ||||||
|  | 		woob = strstr(cmd, ".oob"); | ||||||
|  | 		write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); | ||||||
|  | 
 | ||||||
|  | 		if (!dump) { | ||||||
|  | 			if (!argc) | ||||||
|  | 				return CMD_RET_USAGE; | ||||||
|  | 
 | ||||||
|  | 			user_addr = simple_strtoul(argv[0], NULL, 16); | ||||||
|  | 			argc--; | ||||||
|  | 			argv++; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; | ||||||
|  | 		if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { | ||||||
|  | 			printf("Offset not aligned with a page (0x%x)\n", | ||||||
|  | 			       mtd->writesize); | ||||||
|  | 			return CMD_RET_FAILURE; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		default_len = dump ? mtd->writesize : mtd->size; | ||||||
|  | 		len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : | ||||||
|  | 				 default_len; | ||||||
|  | 		if (!mtd_is_aligned_with_min_io_size(mtd, len)) { | ||||||
|  | 			len = round_up(len, mtd->writesize); | ||||||
|  | 			printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", | ||||||
|  | 			       mtd->writesize, len); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		remaining = len; | ||||||
|  | 		npages = mtd_len_to_pages(mtd, len); | ||||||
|  | 		oob_len = woob ? npages * mtd->oobsize : 0; | ||||||
|  | 
 | ||||||
|  | 		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; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (has_pages) | ||||||
|  | 			printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", | ||||||
|  | 			       read ? "Reading" : "Writing", len, npages, start_off, | ||||||
|  | 			       raw ? " [raw]" : "", woob ? " [oob]" : "", | ||||||
|  | 			       !read && write_empty_pages ? " [dontskipff]" : ""); | ||||||
|  | 		else | ||||||
|  | 			printf("%s %lld byte(s) at offset 0x%08llx\n", | ||||||
|  | 			       read ? "Reading" : "Writing", len, start_off); | ||||||
|  | 
 | ||||||
|  | 		io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; | ||||||
|  | 		io_op.len = has_pages ? mtd->writesize : len; | ||||||
|  | 		io_op.ooblen = woob ? mtd->oobsize : 0; | ||||||
|  | 		io_op.datbuf = buf; | ||||||
|  | 		io_op.oobbuf = woob ? &buf[len] : NULL; | ||||||
|  | 
 | ||||||
|  | 		/* Search for the first good block after the given offset */ | ||||||
|  | 		off = start_off; | ||||||
|  | 		while (mtd_block_isbad(mtd, off)) | ||||||
|  | 			off += mtd->erasesize; | ||||||
|  | 
 | ||||||
|  | 		/* Loop over the pages to do the actual read/write */ | ||||||
|  | 		while (remaining) { | ||||||
|  | 			/* Skip the block if it is bad */ | ||||||
|  | 			if (mtd_is_aligned_with_block_size(mtd, off) && | ||||||
|  | 			    mtd_block_isbad(mtd, off)) { | ||||||
|  | 				off += mtd->erasesize; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (read) | ||||||
|  | 				ret = mtd_read_oob(mtd, off, &io_op); | ||||||
|  | 			else | ||||||
|  | 				ret = mtd_special_write_oob(mtd, off, &io_op, | ||||||
|  | 							    write_empty_pages, | ||||||
|  | 							    woob); | ||||||
|  | 
 | ||||||
|  | 			if (ret) { | ||||||
|  | 				printf("Failure while %s at offset 0x%llx\n", | ||||||
|  | 				       read ? "reading" : "writing", off); | ||||||
|  | 				return CMD_RET_FAILURE; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			off += io_op.retlen; | ||||||
|  | 			remaining -= io_op.retlen; | ||||||
|  | 			io_op.datbuf += io_op.retlen; | ||||||
|  | 			io_op.oobbuf += io_op.oobretlen; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!ret && dump) | ||||||
|  | 			mtd_dump_device_buf(mtd, start_off, buf, len, woob); | ||||||
|  | 
 | ||||||
|  | 		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; | ||||||
|  | 		int ret; | ||||||
|  | 
 | ||||||
|  | 		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 a 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; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (ret && ret != -EIO) | ||||||
|  | 			return CMD_RET_FAILURE; | ||||||
|  | 	} else if (!strcmp(cmd, "bad")) { | ||||||
|  | 		loff_t off; | ||||||
|  | 
 | ||||||
|  | 		if (!mtd_can_have_bb(mtd)) { | ||||||
|  | 			printf("Only NAND-based devices can have bad blocks\n"); | ||||||
|  | 			return CMD_RET_SUCCESS; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		printf("MTD device %s bad blocks list:\n", mtd->name); | ||||||
|  | 		for (off = 0; off < mtd->size; off += mtd->erasesize) | ||||||
|  | 			if (mtd_block_isbad(mtd, off)) | ||||||
|  | 				printf("\t0x%08llx\n", off); | ||||||
|  | 	} else { | ||||||
|  | 		return CMD_RET_USAGE; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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][.dontskipff]    <name> <addr> [<off> [<size>]]\n" | ||||||
|  | 	"mtd erase[.dontskipbad]               <name>        [<off> [<size>]]\n" | ||||||
|  | 	"\n" | ||||||
|  | 	"Specific functions:\n" | ||||||
|  | 	"mtd bad                               <name>\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" | ||||||
|  | 	"\n" | ||||||
|  | 	"The .dontskipff option forces writing empty pages, don't use it if unsure.\n" | ||||||
|  | #endif | ||||||
|  | 	""; | ||||||
|  | 
 | ||||||
|  | U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text); | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| # (C) Copyright 2000-2007
 | # (C) Copyright 2000-2007
 | ||||||
| # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 | # Wolfgang Denk, DENX Software Engineering, wd@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 | obj-y += mtdcore.o mtd_uboot.o | ||||||
| endif | endif | ||||||
| obj-$(CONFIG_MTD) += mtd-uclass.o | obj-$(CONFIG_MTD) += mtd-uclass.o | ||||||
|  |  | ||||||
|  | @ -4,8 +4,15 @@ | ||||||
|  * Heiko Schocher, DENX Software Engineering, hs@denx.de. |  * Heiko Schocher, DENX Software Engineering, hs@denx.de. | ||||||
|  */ |  */ | ||||||
| #include <common.h> | #include <common.h> | ||||||
|  | #include <dm/device.h> | ||||||
|  | #include <dm/uclass-internal.h> | ||||||
|  | #include <jffs2/jffs2.h> /* LEGACY */ | ||||||
| #include <linux/mtd/mtd.h> | #include <linux/mtd/mtd.h> | ||||||
| #include <jffs2/jffs2.h> /* Legacy */ | #include <linux/mtd/partitions.h> | ||||||
|  | #include <mtd.h> | ||||||
|  | 
 | ||||||
|  | #define MTD_NAME_MAX_LEN 20 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to |  * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to | ||||||
|  | @ -68,6 +75,158 @@ int mtd_search_alternate_name(const char *mtdname, char *altname, | ||||||
| 	return -EINVAL; | 	return -EINVAL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #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 | ||||||
|  | 
 | ||||||
|  | #if defined(CONFIG_MTD_PARTITIONS) | ||||||
|  | int mtd_probe_devices(void) | ||||||
|  | { | ||||||
|  | 	static char *old_mtdparts; | ||||||
|  | 	static char *old_mtdids; | ||||||
|  | 	const char *mtdparts = env_get("mtdparts"); | ||||||
|  | 	const char *mtdids = env_get("mtdids"); | ||||||
|  | 	bool remaining_partitions = true; | ||||||
|  | 	struct mtd_info *mtd; | ||||||
|  | 
 | ||||||
|  | 	mtd_probe_uclass_mtd_devs(); | ||||||
|  | 
 | ||||||
|  | 	/* Check if mtdparts/mtdids changed since last call, otherwise: exit */ | ||||||
|  | 	if (!strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	/* Update the local copy of mtdparts */ | ||||||
|  | 	free(old_mtdparts); | ||||||
|  | 	free(old_mtdids); | ||||||
|  | 	old_mtdparts = strdup(mtdparts); | ||||||
|  | 	old_mtdids = strdup(mtdids); | ||||||
|  | 
 | ||||||
|  | 	/* 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 -EACCES; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * 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 mtd_name[MTD_NAME_MAX_LEN], *colon; | ||||||
|  | 		struct mtd_partition *parts; | ||||||
|  | 		int mtd_name_len, nparts; | ||||||
|  | 		int ret; | ||||||
|  | 
 | ||||||
|  | 		colon = strchr(mtdparts, ':'); | ||||||
|  | 		if (!colon) { | ||||||
|  | 			printf("Wrong mtdparts: %s\n", mtdparts); | ||||||
|  | 			return -EINVAL; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		mtd_name_len = colon - mtdparts; | ||||||
|  | 		strncpy(mtd_name, mtdparts, mtd_name_len); | ||||||
|  | 		mtd_name[mtd_name_len] = '\0'; | ||||||
|  | 		/* Move the pointer forward (including the ':') */ | ||||||
|  | 		mtdparts += mtd_name_len + 1; | ||||||
|  | 		mtd = get_mtd_device_nm(mtd_name); | ||||||
|  | 		if (IS_ERR_OR_NULL(mtd)) { | ||||||
|  | 			char linux_name[MTD_NAME_MAX_LEN]; | ||||||
|  | 
 | ||||||
|  | 			/*
 | ||||||
|  | 			 * The MTD device named "mtd_name" does not exist. Try | ||||||
|  | 			 * to find a correspondance with an MTD device having | ||||||
|  | 			 * the same type and number as defined in the mtdids. | ||||||
|  | 			 */ | ||||||
|  | 			debug("No device named %s\n", mtd_name); | ||||||
|  | 			ret = mtd_search_alternate_name(mtd_name, linux_name, | ||||||
|  | 							MTD_NAME_MAX_LEN); | ||||||
|  | 			if (!ret) | ||||||
|  | 				mtd = get_mtd_device_nm(linux_name); | ||||||
|  | 
 | ||||||
|  | 			/*
 | ||||||
|  | 			 * If no device could be found, move the mtdparts | ||||||
|  | 			 * pointer forward until the next set of partitions. | ||||||
|  | 			 */ | ||||||
|  | 			if (ret || IS_ERR_OR_NULL(mtd)) { | ||||||
|  | 				printf("Could not find a valid device for %s\n", | ||||||
|  | 				       mtd_name); | ||||||
|  | 				mtdparts = strchr(mtdparts, ';'); | ||||||
|  | 				if (mtdparts) | ||||||
|  | 					mtdparts++; | ||||||
|  | 
 | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/*
 | ||||||
|  | 		 * 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, &nparts); | ||||||
|  | 		if (ret) { | ||||||
|  | 			printf("Could not parse device %s\n", mtd->name); | ||||||
|  | 			put_mtd_device(mtd); | ||||||
|  | 			return -EINVAL; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!nparts) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		/* Create the new MTD partitions */ | ||||||
|  | 		add_mtd_partitions(mtd, parts, nparts); | ||||||
|  | 
 | ||||||
|  | 		/* Free the structures allocated during the parsing */ | ||||||
|  | 		mtd_free_parsed_partitions(parts, nparts); | ||||||
|  | 
 | ||||||
|  | 		put_mtd_device(mtd); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | #else | ||||||
|  | int mtd_probe_devices(void) | ||||||
|  | { | ||||||
|  | 	mtd_probe_uclass_mtd_devs(); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | #endif /* defined(CONFIG_MTD_PARTITIONS) */ | ||||||
|  | 
 | ||||||
| /* Legacy */ | /* Legacy */ | ||||||
| 
 | 
 | ||||||
| static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size, | static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size, | ||||||
|  |  | ||||||
|  | @ -9,5 +9,6 @@ | ||||||
| #include <linux/mtd/mtd.h> | #include <linux/mtd/mtd.h> | ||||||
| 
 | 
 | ||||||
| int mtd_probe(struct udevice *dev); | int mtd_probe(struct udevice *dev); | ||||||
|  | int mtd_probe_devices(void); | ||||||
| 
 | 
 | ||||||
| #endif	/* _MTD_H_ */ | #endif	/* _MTD_H_ */ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue