mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-19 11:24:42 +00:00
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:
parent
0de1022d88
commit
6015af28ee
6 changed files with 274 additions and 1 deletions
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
232
drivers/dfu/dfu_mtd.c
Normal 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;
|
||||||
|
}
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Reference in a new issue