From mboxrd@z Thu Jan 1 00:00:00 1970 From: Miquel Raynal Date: Wed, 1 Aug 2018 10:18:47 +0200 Subject: [U-Boot] [PATCH v6 26/27] cmd: mtd: add 'mtd' command In-Reply-To: <20180801081848.19398-1-miquel.raynal@bootlin.com> References: <20180801081848.19398-1-miquel.raynal@bootlin.com> Message-ID: <20180801081848.19398-27-miquel.raynal@bootlin.com> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable To: u-boot@lists.denx.de There should not be a 'nand' command, a 'sf' command and certainly not another 'spi-nand'. Write a 'mtd' command instead to manage all MTD devices at once. This should be the preferred way to access any MTD device. Signed-off-by: Miquel Raynal Acked-by: Jagan Teki --- cmd/Kconfig | 7 + cmd/Makefile | 1 + cmd/mtd.c | 392 ++++++++++++++++++++++++++++++++++++++++++++= ++++ drivers/mtd/Makefile | 2 +- include/linux/mtd/mtd.h | 1 + 5 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 cmd/mtd.c diff --git a/cmd/Kconfig b/cmd/Kconfig index 0cf530d923..45ead0ffce 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -848,6 +848,13 @@ config CMD_MMC_SWRITE Enable support for the "mmc swrite" command to write Android sparse images to eMMC. =20 +config CMD_MTD + bool "mtd" + depends on CMD_MTDPARTS + select MTD_PARTITIONS + help + MTD commands support. + config CMD_NAND bool "nand" default y if NAND_SUNXI 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) +=3D misc.o obj-$(CONFIG_CMD_MMC) +=3D mmc.o obj-$(CONFIG_CMD_MMC_SPI) +=3D mmc_spi.o obj-$(CONFIG_MP) +=3D mp.o +obj-$(CONFIG_CMD_MTD) +=3D mtd.o obj-$(CONFIG_CMD_MTDPARTS) +=3D mtdparts.o obj-$(CONFIG_CMD_NAND) +=3D nand.o obj-$(CONFIG_CMD_NET) +=3D net.o diff --git a/cmd/mtd.c b/cmd/mtd.c new file mode 100644 index 0000000000..221b12500f --- /dev/null +++ b/cmd/mtd.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mtd.c + * + * Generic command to handle basic operations on any memory device. + * + * Copyright: Bootlin, 2018 + * Author: Miqu=C3=A8l Raynal + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void mtd_dump_buf(u8 *buf, uint len, uint offset) +{ + int i, j; + + for (i =3D 0; i < len; ) { + printf("0x%08x:\t", offset + i); + for (j =3D 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf(" "); + i +=3D 8; + for (j =3D 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf("\n"); + i +=3D 8; + } +} + +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 information */ + printf("\t> 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("\t> Size: 0x%llx bytes\n", mtd->size); + printf("\t> Block: 0x%x bytes\n", mtd->erasesize); + printf("\t> Min I/O: 0x%x bytes\n", mtd->writesize); + + if (mtd->oobsize) { + printf("\t> OOB size: %u bytes\n", mtd->oobsize); + printf("\t> OOB available: %u bytes\n", mtd->oobavail); + } + + if (mtd->ecc_strength) { + printf("\t> ECC strength: %u bits\n", mtd->ecc_strength); + printf("\t> ECC step size: %u bytes\n", mtd->ecc_step_size); + printf("\t> Bitflip threshold: %u bits\n", + mtd->bitflip_threshold); + } +} + +int mtd_probe_devices(void) +{ + const char *mtdparts =3D env_get("mtdparts"); + struct udevice *dev; + int idx =3D 0; + + /* Probe devices with DM compliant drivers */ + while (!uclass_find_device(UCLASS_MTD, idx, &dev) && dev) { + mtd_probe(dev); + idx++; + } + + /* Create the partitions defined in mtdparts, here the fun begins */ + + /* Ignore the extra 'mtdparts=3D' prefix if any */ + if (strstr(mtdparts, "mtdparts=3D")) + mtdparts +=3D 9; + + /* For each MTD device in mtdparts */ + while (mtdparts[0] !=3D '\0') { + struct mtd_partition *parts; + struct mtd_info *parent; + char mtdname[20]; + int mtdname_len, len, ret; + + mtdname_len =3D strchr(mtdparts, ':') - mtdparts; + strncpy(mtdname, mtdparts, mtdname_len); + mtdname[mtdname_len] =3D '\0'; + /* Move the pointer forward (including the ':') */ + mtdparts +=3D mtdname_len + 1; + parent =3D get_mtd_device_nm(mtdname); + if (IS_ERR_OR_NULL(parent)) { + printf("No device named %s\n", mtdname); + return -EINVAL; + } + + /* + * 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 =3D mtdparts_parse_part(parent, &mtdparts, &parts, &len); + if (ret) { + printf("Could not parse device %s\n", mtdname); + return -EINVAL; + } + + /* + * If there is already an MTD device named after the first + * partition name, this is probably because the registration + * already happened: don't add the partitions again. + */ + if (len && !IS_ERR_OR_NULL(get_mtd_device_nm(parts[0].name))) + continue; + + add_mtd_partitions(parent, parts, len); + free(parts); + } + + return 0; +} + +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 =3D 0; + + /* Ensure all devices (and their partitions) are probed */ + mtd_probe_devices(); + + printf("List of MTD devices:\n"); + mtd_for_each_device(mtd) { + mtd_show_device(mtd); + dev_nb++; + } + + if (!dev_nb) { + printf("No MTD device found\n"); + return -EINVAL; + } + + return 0; +} + +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 =3D argv[1]; + + /* List the MTD devices if that is what the user wants */ + if (strcmp(cmd, "list") =3D=3D 0) + return do_mtd_list(); + + /* + * The remaining commands require also@least a device ID. + * Check the selected device is valid. Ensure it is probed. + */ + if (argc < 3) + return CMD_RET_USAGE; + + mtdname =3D argv[2]; + mtd =3D get_mtd_device_nm(mtdname); + if (IS_ERR_OR_NULL(mtd)) { + mtd_probe_devices(); + mtd =3D 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 1; + } + } + + argc -=3D 3; + argv +=3D 3; + + /* Do the parsing */ + if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) || + !strncmp(cmd, "write", 5)) { + struct mtd_oob_ops io_op =3D {}; + bool dump, read, raw, woob; + uint user_addr =3D 0; + uint nb_pages; + u8 *buf; + u64 off, len, default_len; + u32 oob_len; + + dump =3D !strncmp(cmd, "dump", 4); + read =3D dump || !strncmp(cmd, "read", 4); + raw =3D strstr(cmd, ".raw"); + woob =3D strstr(cmd, ".oob"); + + if (!dump) { + if (!argc) + return CMD_RET_USAGE; + + user_addr =3D simple_strtoul(argv[0], NULL, 16); + argc--; + argv++; + } + + default_len =3D dump ? mtd->writesize : mtd->size; + off =3D argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; + len =3D argc > 1 ? simple_strtoul(argv[1], NULL, 16) : + default_len; + nb_pages =3D mtd_len_to_pages(mtd, len); + oob_len =3D 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 -EINVAL; + } + + if (mtd_is_aligned_with_min_io_size(mtd, len)) { + printf("Size not a multiple of a page (0x%x)\n", + mtd->writesize); + return -EINVAL; + } + + if (dump) + buf =3D kmalloc(len + oob_len, GFP_KERNEL); + else + buf =3D map_sysmem(user_addr, 0); + + if (!buf) { + printf("Could not map/allocate the user buffer\n"); + return -ENOMEM; + } + + 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 =3D raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; + io_op.len =3D len; + io_op.ooblen =3D oob_len; + io_op.datbuf =3D buf; + io_op.oobbuf =3D woob ? &buf[len] : NULL; + + if (read) + ret =3D mtd_read_oob(mtd, off, &io_op); + else + ret =3D mtd_write_oob(mtd, off, &io_op); + + if (ret) { + printf("%s on %s failed with error %d\n", + read ? "Read" : "Write", mtd->name, ret); + return ret; + } + + if (dump) { + uint page; + + for (page =3D 0; page < nb_pages; page++) { + u64 data_off =3D 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 =3D 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); + + } else if (!strcmp(cmd, "erase")) { + bool scrub =3D strstr(cmd, ".dontskipbad"); + struct erase_info erase_op =3D {}; + u64 off, len; + + off =3D argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; + len =3D 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 -EINVAL; + } + + if (mtd_is_aligned_with_block_size(mtd, len)) { + printf("Size not a multiple of a block (0x%x)\n", + mtd->erasesize); + return -EINVAL; + } + + erase_op.mtd =3D mtd; + erase_op.addr =3D off; + erase_op.len =3D len; + erase_op.scrub =3D scrub; + + ret =3D mtd_erase(mtd, &erase_op); + } else { + return CMD_RET_USAGE; + } + + return ret; +} + +static char mtd_help_text[] =3D +#ifdef CONFIG_SYS_LONGHELP + "- generic operations on memory technology devices\n\n" + "mtd list\n" + "mtd read[.raw][.oob] [ []]\n" + "mtd dump[.raw][.oob] [ []]\n" + "mtd write[.raw][.oob] [ []]\n" + "mtd erase[.dontskipbad] [ []]\n" + "\n" + "With:\n" + "\t: NAND partition/chip name\n" + "\t: user address from/to which data will be retrieved/stored\n" + "\t: offset in 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: 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. =20 -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 +=3D mtdcore.o mtd_uboot.o endif obj-$(CONFIG_MTD) +=3D mtd-uclass.o diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 7dd45bde65..4ca5d764d7 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -535,6 +535,7 @@ void mtd_get_len_incl_bad(struct mtd_info *mtd, uint64_= t offset, int *truncated); =20 int mtd_probe(struct udevice *dev); +int mtd_probe_devices(void); =20 #endif #endif /* __MTD_MTD_H__ */ --=20 2.14.1