blob: 485586989c8a60007c1dff7963b685f7edc21457 [file] [log] [blame]
Patrick Delaunay6015af22019-10-14 09:28:04 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * dfu_mtd.c -- DFU for MTD device.
4 *
5 * Copyright (C) 2019,STMicroelectronics - All Rights Reserved
6 *
7 * Based on dfu_nand.c
8 */
9
10#include <common.h>
11#include <dfu.h>
12#include <mtd.h>
Simon Glass61b29b82020-02-03 07:36:15 -070013#include <linux/err.h>
Masami Hiramatsu8db74c12022-01-31 11:52:29 +090014#include <linux/ctype.h>
Patrick Delaunay6015af22019-10-14 09:28:04 +020015
16static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
17{
18 return !do_div(size, mtd->erasesize);
19}
20
Patrick Delaunay56227472022-01-18 10:26:21 +010021/* Logic taken from cmd/mtd.c:mtd_oob_write_is_empty() */
22static bool mtd_page_is_empty(struct mtd_oob_ops *op)
23{
24 int i;
25
26 for (i = 0; i < op->len; i++)
27 if (op->datbuf[i] != 0xff)
28 return false;
29
30 /* oob is not used, with MTD_OPS_AUTO_OOB & ooblen=0 */
31
32 return true;
33}
34
Patrick Delaunay6015af22019-10-14 09:28:04 +020035static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
36 u64 offset, void *buf, long *len)
37{
Sughosh Ganu65f3fc12020-12-30 19:27:06 +053038 u64 off, lim, remaining, lock_ofs, lock_len;
Patrick Delaunay6015af22019-10-14 09:28:04 +020039 struct mtd_info *mtd = dfu->data.mtd.info;
40 struct mtd_oob_ops io_op = {};
41 int ret = 0;
42 bool has_pages = mtd->type == MTD_NANDFLASH ||
43 mtd->type == MTD_MLCNANDFLASH;
44
45 /* if buf == NULL return total size of the area */
46 if (!buf) {
47 *len = dfu->data.mtd.size;
48 return 0;
49 }
50
Sughosh Ganu65f3fc12020-12-30 19:27:06 +053051 off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
Patrick Delaunay6015af22019-10-14 09:28:04 +020052 lim = dfu->data.mtd.start + dfu->data.mtd.size;
53
54 if (off >= lim) {
55 printf("Limit reached 0x%llx\n", lim);
56 *len = 0;
57 return op == DFU_OP_READ ? 0 : -EIO;
58 }
59 /* limit request with the available size */
60 if (off + *len >= lim)
61 *len = lim - off;
62
63 if (!mtd_is_aligned_with_block_size(mtd, off)) {
64 printf("Offset not aligned with a block (0x%x)\n",
65 mtd->erasesize);
66 return 0;
67 }
68
69 /* first erase */
70 if (op == DFU_OP_WRITE) {
71 struct erase_info erase_op = {};
72
Sughosh Ganu65f3fc12020-12-30 19:27:06 +053073 remaining = lock_len = round_up(*len, mtd->erasesize);
Patrick Delaunay6015af22019-10-14 09:28:04 +020074 erase_op.mtd = mtd;
75 erase_op.addr = off;
76 erase_op.len = mtd->erasesize;
77 erase_op.scrub = 0;
78
Sughosh Ganu65f3fc12020-12-30 19:27:06 +053079 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");
83 return 0;
84 }
85
Patrick Delaunay6015af22019-10-14 09:28:04 +020086 while (remaining) {
87 if (erase_op.addr + remaining > lim) {
Patrick Delaunay4b1c0672023-06-05 09:52:07 +020088 printf("Limit reached 0x%llx while erasing at offset 0x%llx, remaining 0x%llx\n",
89 lim, erase_op.addr, remaining);
Patrick Delaunay6015af22019-10-14 09:28:04 +020090 return -EIO;
91 }
92
Patrick Delaunaybe0da122023-06-05 09:52:08 +020093 /* Skip the block if it is bad, don't erase it again */
94 ret = mtd_block_isbad(mtd, erase_op.addr);
95 if (ret) {
96 printf("Skipping %s at 0x%08llx\n",
97 ret == 1 ? "bad block" : "bbt reserved",
98 erase_op.addr);
99 erase_op.addr += mtd->erasesize;
100 continue;
101 }
102
Patrick Delaunay6015af22019-10-14 09:28:04 +0200103 ret = mtd_erase(mtd, &erase_op);
104
105 if (ret) {
Patrick Delaunaybe0da122023-06-05 09:52:08 +0200106 /* If this is not -EIO, we have no idea what to do. */
107 if (ret == -EIO) {
108 printf("Marking bad block at 0x%08llx (%d)\n",
109 erase_op.fail_addr, ret);
110 ret = mtd_block_markbad(mtd, erase_op.addr);
Patrick Delaunay6015af22019-10-14 09:28:04 +0200111 }
Patrick Delaunaybe0da122023-06-05 09:52:08 +0200112 /* Abort if it is not -EIO or can't mark bad */
113 if (ret) {
114 printf("Failure while erasing at offset 0x%llx (%d)\n",
115 erase_op.fail_addr, ret);
116 return ret;
117 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200118 } else {
119 remaining -= mtd->erasesize;
120 }
121
Patrick Delaunaybe0da122023-06-05 09:52:08 +0200122 /* Continue erase behind the current block */
Patrick Delaunay6015af22019-10-14 09:28:04 +0200123 erase_op.addr += mtd->erasesize;
124 }
125 }
126
127 io_op.mode = MTD_OPS_AUTO_OOB;
128 io_op.len = *len;
129 if (has_pages && io_op.len > mtd->writesize)
130 io_op.len = mtd->writesize;
131 io_op.ooblen = 0;
132 io_op.datbuf = buf;
133 io_op.oobbuf = NULL;
134
135 /* Loop over to do the actual read/write */
136 remaining = *len;
137 while (remaining) {
138 if (off + remaining > lim) {
139 printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
140 lim, op == DFU_OP_READ ? "reading" : "writing",
141 off);
142 if (op == DFU_OP_READ) {
143 *len -= remaining;
144 return 0;
145 } else {
146 return -EIO;
147 }
148 }
149
150 /* Skip the block if it is bad */
151 if (mtd_is_aligned_with_block_size(mtd, off) &&
152 mtd_block_isbad(mtd, off)) {
153 off += mtd->erasesize;
154 dfu->bad_skip += mtd->erasesize;
155 continue;
156 }
157
158 if (op == DFU_OP_READ)
159 ret = mtd_read_oob(mtd, off, &io_op);
Patrick Delaunay56227472022-01-18 10:26:21 +0100160 else if (has_pages && dfu->data.mtd.ubi && mtd_page_is_empty(&io_op)) {
161 /* in case of ubi partition, do not write an empty page, only skip it */
162 ret = 0;
163 io_op.retlen = mtd->writesize;
164 io_op.oobretlen = mtd->oobsize;
165 } else {
Patrick Delaunay6015af22019-10-14 09:28:04 +0200166 ret = mtd_write_oob(mtd, off, &io_op);
Patrick Delaunay56227472022-01-18 10:26:21 +0100167 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200168
169 if (ret) {
170 printf("Failure while %s at offset 0x%llx\n",
171 op == DFU_OP_READ ? "reading" : "writing", off);
172 return -EIO;
173 }
174
175 off += io_op.retlen;
176 remaining -= io_op.retlen;
177 io_op.datbuf += io_op.retlen;
178 io_op.len = remaining;
179 if (has_pages && io_op.len > mtd->writesize)
180 io_op.len = mtd->writesize;
181 }
182
Sughosh Ganu65f3fc12020-12-30 19:27:06 +0530183 if (op == DFU_OP_WRITE) {
184 /* Write done, lock again */
185 debug("Locking the mtd device\n");
186 ret = mtd_lock(mtd, lock_ofs, lock_len);
Patrick Delaunaya5bb3842021-03-10 10:27:22 +0100187 if (ret == -EOPNOTSUPP)
188 ret = 0;
189 else if (ret)
Sughosh Ganu65f3fc12020-12-30 19:27:06 +0530190 printf("MTD device lock failed\n");
191 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200192 return ret;
193}
194
195static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
196{
197 *size = dfu->data.mtd.info->size;
198
199 return 0;
200}
201
202static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
203 long *len)
204{
205 int ret = -1;
206
207 switch (dfu->layout) {
208 case DFU_RAW_ADDR:
209 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
210 break;
211 default:
212 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
213 dfu_get_layout(dfu->layout));
214 }
215
216 return ret;
217}
218
219static int dfu_write_medium_mtd(struct dfu_entity *dfu,
220 u64 offset, void *buf, long *len)
221{
222 int ret = -1;
223
224 switch (dfu->layout) {
225 case DFU_RAW_ADDR:
226 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
227 break;
228 default:
229 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
230 dfu_get_layout(dfu->layout));
231 }
232
233 return ret;
234}
235
236static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
237{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200238 struct mtd_info *mtd = dfu->data.mtd.info;
239 u64 remaining;
240 int ret;
241
242 /* in case of ubi partition, erase rest of the partition */
Guillermo Rodriguez1b3c4cb2020-09-02 13:06:06 +0200243 if (dfu->data.mtd.ubi) {
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200244 struct erase_info erase_op = {};
245
246 erase_op.mtd = dfu->data.mtd.info;
247 erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
248 dfu->bad_skip, mtd->erasesize);
249 erase_op.len = mtd->erasesize;
250 erase_op.scrub = 0;
251
252 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
253 erase_op.addr;
254
255 while (remaining) {
256 ret = mtd_erase(mtd, &erase_op);
257
258 if (ret) {
259 /* Abort if its not a bad block error */
260 if (ret != -EIO)
261 break;
262 printf("Skipping bad block at 0x%08llx\n",
263 erase_op.addr);
264 }
265
266 /* Skip bad block and continue behind it */
267 erase_op.addr += mtd->erasesize;
268 remaining -= mtd->erasesize;
269 }
270 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200271 return 0;
272}
273
274static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
275{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200276 /*
277 * Currently, Poll Timeout != 0 is only needed on nand
278 * ubi partition, as sectors which are not used need
279 * to be erased
280 */
Guillermo Rodriguez1b3c4cb2020-09-02 13:06:06 +0200281 if (dfu->data.mtd.ubi)
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200282 return DFU_MANIFEST_POLL_TIMEOUT;
283
Patrick Delaunay6015af22019-10-14 09:28:04 +0200284 return DFU_DEFAULT_POLL_TIMEOUT;
285}
286
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900287int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char **argv, int argc)
Patrick Delaunay6015af22019-10-14 09:28:04 +0200288{
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900289 char *s;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200290 struct mtd_info *mtd;
Patrick Delaunay28d34392023-06-08 17:16:39 +0200291 int part;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200292
293 mtd = get_mtd_device_nm(devstr);
294 if (IS_ERR_OR_NULL(mtd))
295 return -ENODEV;
296 put_mtd_device(mtd);
297
298 dfu->dev_type = DFU_DEV_MTD;
299 dfu->data.mtd.info = mtd;
Patrick Delaunay2dc41fc2021-03-04 17:47:56 +0100300 dfu->max_buf_size = mtd->erasesize;
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900301 if (argc < 1)
302 return -EINVAL;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200303
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900304 if (!strcmp(argv[0], "raw")) {
305 if (argc != 3)
306 return -EINVAL;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200307 dfu->layout = DFU_RAW_ADDR;
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900308 dfu->data.mtd.start = hextoul(argv[1], &s);
309 if (*s)
310 return -EINVAL;
311 dfu->data.mtd.size = hextoul(argv[2], &s);
312 if (*s)
313 return -EINVAL;
314 } else if ((!strcmp(argv[0], "part")) || (!strcmp(argv[0], "partubi"))) {
Patrick Delaunay28d34392023-06-08 17:16:39 +0200315 struct mtd_info *partition;
316 int partnum = 0;
317 bool part_found = false;
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200318
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900319 if (argc != 2)
320 return -EINVAL;
321
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200322 dfu->layout = DFU_RAW_ADDR;
323
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900324 part = dectoul(argv[1], &s);
325 if (*s)
326 return -EINVAL;
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200327
Patrick Delaunay28d34392023-06-08 17:16:39 +0200328 /* register partitions with MTDIDS/MTDPARTS or OF fallback */
329 mtd_probe_devices();
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200330
Patrick Delaunay28d34392023-06-08 17:16:39 +0200331 partnum = 0;
332 list_for_each_entry(partition, &mtd->partitions, node) {
333 partnum++;
334 if (partnum == part) {
335 part_found = true;
336 break;
337 }
338 }
339 if (!part_found) {
340 printf("No partition %d in %s\n", part, mtd->name);
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200341 return -1;
342 }
Patrick Delaunay28d34392023-06-08 17:16:39 +0200343 log_debug("partition %d:%s in %s\n", partnum, partition->name, mtd->name);
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200344
Patrick Delaunay28d34392023-06-08 17:16:39 +0200345 dfu->data.mtd.start = partition->offset;
346 dfu->data.mtd.size = partition->size;
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900347 if (!strcmp(argv[0], "partubi"))
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200348 dfu->data.mtd.ubi = 1;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200349 } else {
Masami Hiramatsu53b40632022-01-31 11:52:37 +0900350 printf("%s: Memory layout (%s) not supported!\n", __func__, argv[0]);
Patrick Delaunay6015af22019-10-14 09:28:04 +0200351 return -1;
352 }
353
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200354 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
355 printf("Offset not aligned with a block (0x%x)\n",
356 mtd->erasesize);
357 return -EINVAL;
358 }
359 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
360 printf("Size not aligned with a block (0x%x)\n",
361 mtd->erasesize);
362 return -EINVAL;
363 }
364
Patrick Delaunay6015af22019-10-14 09:28:04 +0200365 dfu->get_medium_size = dfu_get_medium_size_mtd;
366 dfu->read_medium = dfu_read_medium_mtd;
367 dfu->write_medium = dfu_write_medium_mtd;
368 dfu->flush_medium = dfu_flush_medium_mtd;
369 dfu->poll_timeout = dfu_polltimeout_mtd;
370
371 /* initial state */
372 dfu->inited = 0;
373
374 return 0;
375}