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 <jffs2/load_kernel.h>
14 #include <linux/err.h>
16 static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
18 return !do_div(size, mtd->erasesize);
21 static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
22 u64 offset, void *buf, long *len)
24 u64 off, lim, remaining, lock_ofs, lock_len;
25 struct mtd_info *mtd = dfu->data.mtd.info;
26 struct mtd_oob_ops io_op = {};
28 bool has_pages = mtd->type == MTD_NANDFLASH ||
29 mtd->type == MTD_MLCNANDFLASH;
31 /* if buf == NULL return total size of the area */
33 *len = dfu->data.mtd.size;
37 off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
38 lim = dfu->data.mtd.start + dfu->data.mtd.size;
41 printf("Limit reached 0x%llx\n", lim);
43 return op == DFU_OP_READ ? 0 : -EIO;
45 /* limit request with the available size */
46 if (off + *len >= lim)
49 if (!mtd_is_aligned_with_block_size(mtd, off)) {
50 printf("Offset not aligned with a block (0x%x)\n",
56 if (op == DFU_OP_WRITE) {
57 struct erase_info erase_op = {};
59 remaining = lock_len = round_up(*len, mtd->erasesize);
62 erase_op.len = mtd->erasesize;
65 debug("Unlocking the mtd device\n");
66 ret = mtd_unlock(mtd, lock_ofs, lock_len);
67 if (ret && ret != -EOPNOTSUPP) {
68 printf("MTD device unlock failed\n");
73 if (erase_op.addr + remaining > lim) {
74 printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
79 ret = mtd_erase(mtd, &erase_op);
82 /* Abort if its not a bad block error */
84 printf("Failure while erasing at offset 0x%llx\n",
88 printf("Skipping bad block at 0x%08llx\n",
91 remaining -= mtd->erasesize;
94 /* Continue erase behind bad block */
95 erase_op.addr += mtd->erasesize;
99 io_op.mode = MTD_OPS_AUTO_OOB;
101 if (has_pages && io_op.len > mtd->writesize)
102 io_op.len = mtd->writesize;
107 /* Loop over to do the actual read/write */
110 if (off + remaining > lim) {
111 printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
112 lim, op == DFU_OP_READ ? "reading" : "writing",
114 if (op == DFU_OP_READ) {
122 /* Skip the block if it is bad */
123 if (mtd_is_aligned_with_block_size(mtd, off) &&
124 mtd_block_isbad(mtd, off)) {
125 off += mtd->erasesize;
126 dfu->bad_skip += mtd->erasesize;
130 if (op == DFU_OP_READ)
131 ret = mtd_read_oob(mtd, off, &io_op);
133 ret = mtd_write_oob(mtd, off, &io_op);
136 printf("Failure while %s at offset 0x%llx\n",
137 op == DFU_OP_READ ? "reading" : "writing", off);
142 remaining -= io_op.retlen;
143 io_op.datbuf += io_op.retlen;
144 io_op.len = remaining;
145 if (has_pages && io_op.len > mtd->writesize)
146 io_op.len = mtd->writesize;
149 if (op == DFU_OP_WRITE) {
150 /* Write done, lock again */
151 debug("Locking the mtd device\n");
152 ret = mtd_lock(mtd, lock_ofs, lock_len);
153 if (ret && ret != -EOPNOTSUPP)
154 printf("MTD device lock failed\n");
159 static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
161 *size = dfu->data.mtd.info->size;
166 static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
171 switch (dfu->layout) {
173 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
176 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
177 dfu_get_layout(dfu->layout));
183 static int dfu_write_medium_mtd(struct dfu_entity *dfu,
184 u64 offset, void *buf, long *len)
188 switch (dfu->layout) {
190 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
193 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
194 dfu_get_layout(dfu->layout));
200 static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
202 struct mtd_info *mtd = dfu->data.mtd.info;
206 /* in case of ubi partition, erase rest of the partition */
207 if (dfu->data.nand.ubi) {
208 struct erase_info erase_op = {};
210 erase_op.mtd = dfu->data.mtd.info;
211 erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
212 dfu->bad_skip, mtd->erasesize);
213 erase_op.len = mtd->erasesize;
216 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
220 ret = mtd_erase(mtd, &erase_op);
223 /* Abort if its not a bad block error */
226 printf("Skipping bad block at 0x%08llx\n",
230 /* Skip bad block and continue behind it */
231 erase_op.addr += mtd->erasesize;
232 remaining -= mtd->erasesize;
238 static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
241 * Currently, Poll Timeout != 0 is only needed on nand
242 * ubi partition, as sectors which are not used need
245 if (dfu->data.nand.ubi)
246 return DFU_MANIFEST_POLL_TIMEOUT;
248 return DFU_DEFAULT_POLL_TIMEOUT;
251 int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s)
254 struct mtd_info *mtd;
258 mtd = get_mtd_device_nm(devstr);
259 if (IS_ERR_OR_NULL(mtd))
263 dfu->dev_type = DFU_DEV_MTD;
264 dfu->data.mtd.info = mtd;
266 has_pages = mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH;
267 dfu->max_buf_size = has_pages ? mtd->erasesize : 0;
269 st = strsep(&s, " ");
270 if (!strcmp(st, "raw")) {
271 dfu->layout = DFU_RAW_ADDR;
272 dfu->data.mtd.start = simple_strtoul(s, &s, 16);
274 dfu->data.mtd.size = simple_strtoul(s, &s, 16);
275 } else if ((!strcmp(st, "part")) || (!strcmp(st, "partubi"))) {
277 struct mtd_device *mtd_dev;
279 struct part_info *pi;
281 dfu->layout = DFU_RAW_ADDR;
283 part = simple_strtoul(s, &s, 10);
285 sprintf(mtd_id, "%s,%d", devstr, part - 1);
286 printf("using id '%s'\n", mtd_id);
290 ret = find_dev_and_part(mtd_id, &mtd_dev, &part_num, &pi);
292 printf("Could not locate '%s'\n", mtd_id);
296 dfu->data.mtd.start = pi->offset;
297 dfu->data.mtd.size = pi->size;
298 if (!strcmp(st, "partubi"))
299 dfu->data.mtd.ubi = 1;
301 printf("%s: Memory layout (%s) not supported!\n", __func__, st);
305 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
306 printf("Offset not aligned with a block (0x%x)\n",
310 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
311 printf("Size not aligned with a block (0x%x)\n",
316 dfu->get_medium_size = dfu_get_medium_size_mtd;
317 dfu->read_medium = dfu_read_medium_mtd;
318 dfu->write_medium = dfu_write_medium_mtd;
319 dfu->flush_medium = dfu_flush_medium_mtd;
320 dfu->poll_timeout = dfu_polltimeout_mtd;