1 // SPDX-License-Identifier: GPL-2.0+
3 * dfu_mtd.c -- DFU for MTD device.
5 * Copyright (C) 2019,STMicroelectronics - All Rights Reserved
13 #include <linux/err.h>
14 #include <linux/ctype.h>
16 static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
18 return !do_div(size, mtd->erasesize);
21 /* Logic taken from cmd/mtd.c:mtd_oob_write_is_empty() */
22 static bool mtd_page_is_empty(struct mtd_oob_ops *op)
26 for (i = 0; i < op->len; i++)
27 if (op->datbuf[i] != 0xff)
30 /* oob is not used, with MTD_OPS_AUTO_OOB & ooblen=0 */
35 static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
36 u64 offset, void *buf, long *len)
38 u64 off, lim, remaining, lock_ofs, lock_len;
39 struct mtd_info *mtd = dfu->data.mtd.info;
40 struct mtd_oob_ops io_op = {};
42 bool has_pages = mtd->type == MTD_NANDFLASH ||
43 mtd->type == MTD_MLCNANDFLASH;
45 /* if buf == NULL return total size of the area */
47 *len = dfu->data.mtd.size;
51 off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
52 lim = dfu->data.mtd.start + dfu->data.mtd.size;
55 printf("Limit reached 0x%llx\n", lim);
57 return op == DFU_OP_READ ? 0 : -EIO;
59 /* limit request with the available size */
60 if (off + *len >= lim)
63 if (!mtd_is_aligned_with_block_size(mtd, off)) {
64 printf("Offset not aligned with a block (0x%x)\n",
70 if (op == DFU_OP_WRITE) {
71 struct erase_info erase_op = {};
73 remaining = lock_len = round_up(*len, mtd->erasesize);
76 erase_op.len = mtd->erasesize;
79 debug("Unlocking the mtd device\n");
80 ret = mtd_unlock(mtd, lock_ofs, lock_len);
81 if (ret && ret != -EOPNOTSUPP) {
82 printf("MTD device unlock failed\n");
87 if (erase_op.addr + remaining > lim) {
88 printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
93 ret = mtd_erase(mtd, &erase_op);
96 /* Abort if its not a bad block error */
98 printf("Failure while erasing at offset 0x%llx\n",
102 printf("Skipping bad block at 0x%08llx\n",
105 remaining -= mtd->erasesize;
108 /* Continue erase behind bad block */
109 erase_op.addr += mtd->erasesize;
113 io_op.mode = MTD_OPS_AUTO_OOB;
115 if (has_pages && io_op.len > mtd->writesize)
116 io_op.len = mtd->writesize;
121 /* Loop over to do the actual read/write */
124 if (off + remaining > lim) {
125 printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
126 lim, op == DFU_OP_READ ? "reading" : "writing",
128 if (op == DFU_OP_READ) {
136 /* Skip the block if it is bad */
137 if (mtd_is_aligned_with_block_size(mtd, off) &&
138 mtd_block_isbad(mtd, off)) {
139 off += mtd->erasesize;
140 dfu->bad_skip += mtd->erasesize;
144 if (op == DFU_OP_READ)
145 ret = mtd_read_oob(mtd, off, &io_op);
146 else if (has_pages && dfu->data.mtd.ubi && mtd_page_is_empty(&io_op)) {
147 /* in case of ubi partition, do not write an empty page, only skip it */
149 io_op.retlen = mtd->writesize;
150 io_op.oobretlen = mtd->oobsize;
152 ret = mtd_write_oob(mtd, off, &io_op);
156 printf("Failure while %s at offset 0x%llx\n",
157 op == DFU_OP_READ ? "reading" : "writing", off);
162 remaining -= io_op.retlen;
163 io_op.datbuf += io_op.retlen;
164 io_op.len = remaining;
165 if (has_pages && io_op.len > mtd->writesize)
166 io_op.len = mtd->writesize;
169 if (op == DFU_OP_WRITE) {
170 /* Write done, lock again */
171 debug("Locking the mtd device\n");
172 ret = mtd_lock(mtd, lock_ofs, lock_len);
173 if (ret == -EOPNOTSUPP)
176 printf("MTD device lock failed\n");
181 static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
183 *size = dfu->data.mtd.info->size;
188 static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
193 switch (dfu->layout) {
195 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
198 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
199 dfu_get_layout(dfu->layout));
205 static int dfu_write_medium_mtd(struct dfu_entity *dfu,
206 u64 offset, void *buf, long *len)
210 switch (dfu->layout) {
212 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
215 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
216 dfu_get_layout(dfu->layout));
222 static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
224 struct mtd_info *mtd = dfu->data.mtd.info;
228 /* in case of ubi partition, erase rest of the partition */
229 if (dfu->data.mtd.ubi) {
230 struct erase_info erase_op = {};
232 erase_op.mtd = dfu->data.mtd.info;
233 erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
234 dfu->bad_skip, mtd->erasesize);
235 erase_op.len = mtd->erasesize;
238 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
242 ret = mtd_erase(mtd, &erase_op);
245 /* Abort if its not a bad block error */
248 printf("Skipping bad block at 0x%08llx\n",
252 /* Skip bad block and continue behind it */
253 erase_op.addr += mtd->erasesize;
254 remaining -= mtd->erasesize;
260 static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
263 * Currently, Poll Timeout != 0 is only needed on nand
264 * ubi partition, as sectors which are not used need
267 if (dfu->data.mtd.ubi)
268 return DFU_MANIFEST_POLL_TIMEOUT;
270 return DFU_DEFAULT_POLL_TIMEOUT;
273 int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char **argv, int argc)
276 struct mtd_info *mtd;
279 mtd = get_mtd_device_nm(devstr);
280 if (IS_ERR_OR_NULL(mtd))
284 dfu->dev_type = DFU_DEV_MTD;
285 dfu->data.mtd.info = mtd;
286 dfu->max_buf_size = mtd->erasesize;
290 if (!strcmp(argv[0], "raw")) {
293 dfu->layout = DFU_RAW_ADDR;
294 dfu->data.mtd.start = hextoul(argv[1], &s);
297 dfu->data.mtd.size = hextoul(argv[2], &s);
300 } else if ((!strcmp(argv[0], "part")) || (!strcmp(argv[0], "partubi"))) {
301 struct mtd_info *partition;
303 bool part_found = false;
308 dfu->layout = DFU_RAW_ADDR;
310 part = dectoul(argv[1], &s);
314 /* register partitions with MTDIDS/MTDPARTS or OF fallback */
318 list_for_each_entry(partition, &mtd->partitions, node) {
320 if (partnum == part) {
326 printf("No partition %d in %s\n", part, mtd->name);
329 log_debug("partition %d:%s in %s\n", partnum, partition->name, mtd->name);
331 dfu->data.mtd.start = partition->offset;
332 dfu->data.mtd.size = partition->size;
333 if (!strcmp(argv[0], "partubi"))
334 dfu->data.mtd.ubi = 1;
336 printf("%s: Memory layout (%s) not supported!\n", __func__, argv[0]);
340 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
341 printf("Offset not aligned with a block (0x%x)\n",
345 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
346 printf("Size not aligned with a block (0x%x)\n",
351 dfu->get_medium_size = dfu_get_medium_size_mtd;
352 dfu->read_medium = dfu_read_medium_mtd;
353 dfu->write_medium = dfu_write_medium_mtd;
354 dfu->flush_medium = dfu_flush_medium_mtd;
355 dfu->poll_timeout = dfu_polltimeout_mtd;