dfu: add backend for MTD device

Add DFU backend for MTD device: allow to read
and write on all MTD device (NAND, SPI-NOR,
SPI-NAND,...)

For example :
> set dfu_alt_info "nand_raw raw 0x0 0x100000"
> dfu 0 mtd nand0

This MTD backend provides the same level than dfu nand
backend for NAND and dfu sf backend for SPI-NOR;
So it can replace booth of them but it also
add support of spi-nand.

> set dfu_alt_info "nand_raw raw 0x0 0x100000"
> dfu 0 mtd spi-nand0

The backend code is based on the "mtd" command
introduced by commit 5db66b3aee ("cmd: mtd:
add 'mtd' command")

Signed-off-by: Patrick Delaunay <patrick.delaunay@st.com>
This commit is contained in:
Patrick Delaunay 2019-10-14 09:28:04 +02:00 committed by Marek Vasut
parent 0de1022d88
commit 6015af28ee
6 changed files with 274 additions and 1 deletions

View file

@ -21,6 +21,7 @@ Overview:
- NAND - NAND
- RAM - RAM
- SF (serial flash) - SF (serial flash)
- MTD (all MTD device: NAND, SPI-NOR, SPI-NAND,...)
These DFU backends are also used by These DFU backends are also used by
- the dfutftp (see README.dfutftp) - the dfutftp (see README.dfutftp)
@ -30,6 +31,7 @@ Configuration Options:
CONFIG_DFU CONFIG_DFU
CONFIG_DFU_OVER_USB CONFIG_DFU_OVER_USB
CONFIG_DFU_MMC CONFIG_DFU_MMC
CONFIG_DFU_MTD
CONFIG_DFU_NAND CONFIG_DFU_NAND
CONFIG_DFU_RAM CONFIG_DFU_RAM
CONFIG_DFU_SF CONFIG_DFU_SF
@ -104,6 +106,13 @@ Commands:
with <partid> is the MTD partition index with <partid> is the MTD partition index
"mtd" (all MTD device: NAND, SPI-NOR, SPI-NAND,...)
cmd: dfu 0 mtd <dev>
with <dev> the mtd identifier as defined in mtd command
(nand0, nor0, spi-nand0,...)
each element in "dfu_alt_info" =
<name> raw <offset> <size> raw access to mtd device
<interface> and <dev> are absent: <interface> and <dev> are absent:
the dfu command to use multiple devices the dfu command to use multiple devices
cmd: dfu 0 list cmd: dfu 0 list
@ -114,6 +123,7 @@ Commands:
nand <dev>=<alt1>;....;<altN> nand <dev>=<alt1>;....;<altN>
ram <dev>=<alt1>;....;<altN> ram <dev>=<alt1>;....;<altN>
sf <dev>=<alt1>;....;<altN> sf <dev>=<alt1>;....;<altN>
mtd <dev>=<alt1>;....;<altN>
Host tools: Host tools:

View file

@ -54,5 +54,11 @@ config DFU_SF_PART
This option enables the support of "part" and "partubi" target in This option enables the support of "part" and "partubi" target in
SPI flash DFU back end. SPI flash DFU back end.
config DFU_MTD
bool "MTD back end for DFU"
depends on MTD
help
This option enables using DFU to read and write to on any MTD device.
endif endif
endmenu endmenu

View file

@ -5,6 +5,7 @@
obj-$(CONFIG_$(SPL_)DFU) += dfu.o obj-$(CONFIG_$(SPL_)DFU) += dfu.o
obj-$(CONFIG_$(SPL_)DFU_MMC) += dfu_mmc.o obj-$(CONFIG_$(SPL_)DFU_MMC) += dfu_mmc.o
obj-$(CONFIG_$(SPL_)DFU_MTD) += dfu_mtd.o
obj-$(CONFIG_$(SPL_)DFU_NAND) += dfu_nand.o obj-$(CONFIG_$(SPL_)DFU_NAND) += dfu_nand.o
obj-$(CONFIG_$(SPL_)DFU_RAM) += dfu_ram.o obj-$(CONFIG_$(SPL_)DFU_RAM) += dfu_ram.o
obj-$(CONFIG_$(SPL_)DFU_SF) += dfu_sf.o obj-$(CONFIG_$(SPL_)DFU_SF) += dfu_sf.o

View file

@ -462,6 +462,9 @@ static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt,
if (strcmp(interface, "mmc") == 0) { if (strcmp(interface, "mmc") == 0) {
if (dfu_fill_entity_mmc(dfu, devstr, s)) if (dfu_fill_entity_mmc(dfu, devstr, s))
return -1; return -1;
} else if (strcmp(interface, "mtd") == 0) {
if (dfu_fill_entity_mtd(dfu, devstr, s))
return -1;
} else if (strcmp(interface, "nand") == 0) { } else if (strcmp(interface, "nand") == 0) {
if (dfu_fill_entity_nand(dfu, devstr, s)) if (dfu_fill_entity_nand(dfu, devstr, s))
return -1; return -1;
@ -566,7 +569,7 @@ int dfu_config_entities(char *env, char *interface, char *devstr)
const char *dfu_get_dev_type(enum dfu_device_type t) const char *dfu_get_dev_type(enum dfu_device_type t)
{ {
const char *const dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM", const char *const dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM",
"SF"}; "SF", "MTD"};
return dev_t[t]; return dev_t[t];
} }

232
drivers/dfu/dfu_mtd.c Normal file
View file

@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* dfu_mtd.c -- DFU for MTD device.
*
* Copyright (C) 2019,STMicroelectronics - All Rights Reserved
*
* Based on dfu_nand.c
*/
#include <common.h>
#include <dfu.h>
#include <mtd.h>
static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
{
return !do_div(size, mtd->erasesize);
}
static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
u64 offset, void *buf, long *len)
{
u64 off, lim, remaining;
struct mtd_info *mtd = dfu->data.mtd.info;
struct mtd_oob_ops io_op = {};
int ret = 0;
bool has_pages = mtd->type == MTD_NANDFLASH ||
mtd->type == MTD_MLCNANDFLASH;
/* if buf == NULL return total size of the area */
if (!buf) {
*len = dfu->data.mtd.size;
return 0;
}
off = dfu->data.mtd.start + offset + dfu->bad_skip;
lim = dfu->data.mtd.start + dfu->data.mtd.size;
if (off >= lim) {
printf("Limit reached 0x%llx\n", lim);
*len = 0;
return op == DFU_OP_READ ? 0 : -EIO;
}
/* limit request with the available size */
if (off + *len >= lim)
*len = lim - off;
if (!mtd_is_aligned_with_block_size(mtd, off)) {
printf("Offset not aligned with a block (0x%x)\n",
mtd->erasesize);
return 0;
}
/* first erase */
if (op == DFU_OP_WRITE) {
struct erase_info erase_op = {};
remaining = round_up(*len, mtd->erasesize);
erase_op.mtd = mtd;
erase_op.addr = off;
erase_op.len = mtd->erasesize;
erase_op.scrub = 0;
while (remaining) {
if (erase_op.addr + remaining > lim) {
printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
lim, off);
return -EIO;
}
ret = mtd_erase(mtd, &erase_op);
if (ret) {
/* Abort if its not a bad block error */
if (ret != -EIO) {
printf("Failure while erasing at offset 0x%llx\n",
erase_op.fail_addr);
return 0;
}
printf("Skipping bad block at 0x%08llx\n",
erase_op.addr);
} else {
remaining -= mtd->erasesize;
}
/* Continue erase behind bad block */
erase_op.addr += mtd->erasesize;
}
}
io_op.mode = MTD_OPS_AUTO_OOB;
io_op.len = *len;
if (has_pages && io_op.len > mtd->writesize)
io_op.len = mtd->writesize;
io_op.ooblen = 0;
io_op.datbuf = buf;
io_op.oobbuf = NULL;
/* Loop over to do the actual read/write */
remaining = *len;
while (remaining) {
if (off + remaining > lim) {
printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
lim, op == DFU_OP_READ ? "reading" : "writing",
off);
if (op == DFU_OP_READ) {
*len -= remaining;
return 0;
} else {
return -EIO;
}
}
/* Skip the block if it is bad */
if (mtd_is_aligned_with_block_size(mtd, off) &&
mtd_block_isbad(mtd, off)) {
off += mtd->erasesize;
dfu->bad_skip += mtd->erasesize;
continue;
}
if (op == DFU_OP_READ)
ret = mtd_read_oob(mtd, off, &io_op);
else
ret = mtd_write_oob(mtd, off, &io_op);
if (ret) {
printf("Failure while %s at offset 0x%llx\n",
op == DFU_OP_READ ? "reading" : "writing", off);
return -EIO;
}
off += io_op.retlen;
remaining -= io_op.retlen;
io_op.datbuf += io_op.retlen;
io_op.len = remaining;
if (has_pages && io_op.len > mtd->writesize)
io_op.len = mtd->writesize;
}
return ret;
}
static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
{
*size = dfu->data.mtd.info->size;
return 0;
}
static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
long *len)
{
int ret = -1;
switch (dfu->layout) {
case DFU_RAW_ADDR:
ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
break;
default:
printf("%s: Layout (%s) not (yet) supported!\n", __func__,
dfu_get_layout(dfu->layout));
}
return ret;
}
static int dfu_write_medium_mtd(struct dfu_entity *dfu,
u64 offset, void *buf, long *len)
{
int ret = -1;
switch (dfu->layout) {
case DFU_RAW_ADDR:
ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
break;
default:
printf("%s: Layout (%s) not (yet) supported!\n", __func__,
dfu_get_layout(dfu->layout));
}
return ret;
}
static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
{
return 0;
}
static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
{
return DFU_DEFAULT_POLL_TIMEOUT;
}
int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s)
{
char *st;
struct mtd_info *mtd;
bool has_pages;
mtd = get_mtd_device_nm(devstr);
if (IS_ERR_OR_NULL(mtd))
return -ENODEV;
put_mtd_device(mtd);
dfu->dev_type = DFU_DEV_MTD;
dfu->data.mtd.info = mtd;
has_pages = mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH;
dfu->max_buf_size = has_pages ? mtd->erasesize : 0;
st = strsep(&s, " ");
if (!strcmp(st, "raw")) {
dfu->layout = DFU_RAW_ADDR;
dfu->data.mtd.start = simple_strtoul(s, &s, 16);
s++;
dfu->data.mtd.size = simple_strtoul(s, &s, 16);
} else {
printf("%s: (%s) not supported!\n", __func__, st);
return -1;
}
dfu->get_medium_size = dfu_get_medium_size_mtd;
dfu->read_medium = dfu_read_medium_mtd;
dfu->write_medium = dfu_write_medium_mtd;
dfu->flush_medium = dfu_flush_medium_mtd;
dfu->poll_timeout = dfu_polltimeout_mtd;
/* initial state */
dfu->inited = 0;
return 0;
}

View file

@ -22,6 +22,7 @@ enum dfu_device_type {
DFU_DEV_NAND, DFU_DEV_NAND,
DFU_DEV_RAM, DFU_DEV_RAM,
DFU_DEV_SF, DFU_DEV_SF,
DFU_DEV_MTD,
}; };
enum dfu_layout { enum dfu_layout {
@ -55,6 +56,14 @@ struct mmc_internal_data {
unsigned int part; unsigned int part;
}; };
struct mtd_internal_data {
struct mtd_info *info;
/* RAW programming */
u64 start;
u64 size;
};
struct nand_internal_data { struct nand_internal_data {
/* RAW programming */ /* RAW programming */
u64 start; u64 start;
@ -105,6 +114,7 @@ struct dfu_entity {
union { union {
struct mmc_internal_data mmc; struct mmc_internal_data mmc;
struct mtd_internal_data mtd;
struct nand_internal_data nand; struct nand_internal_data nand;
struct ram_internal_data ram; struct ram_internal_data ram;
struct sf_internal_data sf; struct sf_internal_data sf;
@ -249,6 +259,17 @@ static inline int dfu_fill_entity_sf(struct dfu_entity *dfu, char *devstr,
} }
#endif #endif
#if CONFIG_IS_ENABLED(DFU_MTD)
int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s);
#else
static inline int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr,
char *s)
{
puts("MTD support not available!\n");
return -1;
}
#endif
/** /**
* dfu_tftp_write - Write TFTP data to DFU medium * dfu_tftp_write - Write TFTP data to DFU medium
* *