blob: c36ac09189f3e7d0e3eedcdfdcc5b7c1161d4814 [file] [log] [blame]
Patrick Delaunayf7aee232019-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
Patrick Delaunayf7aee232019-10-14 09:28:04 +020010#include <dfu.h>
11#include <mtd.h>
Simon Glassd66c5f72020-02-03 07:36:15 -070012#include <linux/err.h>
Masami Hiramatsuf815c332022-01-31 11:52:29 +090013#include <linux/ctype.h>
Patrick Delaunayf7aee232019-10-14 09:28:04 +020014
15static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
16{
17 return !do_div(size, mtd->erasesize);
18}
19
Patrick Delaunayfd1f2632022-01-18 10:26:21 +010020/* Logic taken from cmd/mtd.c:mtd_oob_write_is_empty() */
21static bool mtd_page_is_empty(struct mtd_oob_ops *op)
22{
23 int i;
24
25 for (i = 0; i < op->len; i++)
26 if (op->datbuf[i] != 0xff)
27 return false;
28
29 /* oob is not used, with MTD_OPS_AUTO_OOB & ooblen=0 */
30
31 return true;
32}
33
Patrick Delaunayf7aee232019-10-14 09:28:04 +020034static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
35 u64 offset, void *buf, long *len)
36{
Sughosh Ganud6c907e2020-12-30 19:27:06 +053037 u64 off, lim, remaining, lock_ofs, lock_len;
Patrick Delaunayf7aee232019-10-14 09:28:04 +020038 struct mtd_info *mtd = dfu->data.mtd.info;
39 struct mtd_oob_ops io_op = {};
40 int ret = 0;
41 bool has_pages = mtd->type == MTD_NANDFLASH ||
42 mtd->type == MTD_MLCNANDFLASH;
43
44 /* if buf == NULL return total size of the area */
45 if (!buf) {
46 *len = dfu->data.mtd.size;
47 return 0;
48 }
49
Sughosh Ganud6c907e2020-12-30 19:27:06 +053050 off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
Patrick Delaunayf7aee232019-10-14 09:28:04 +020051 lim = dfu->data.mtd.start + dfu->data.mtd.size;
52
53 if (off >= lim) {
54 printf("Limit reached 0x%llx\n", lim);
55 *len = 0;
56 return op == DFU_OP_READ ? 0 : -EIO;
57 }
58 /* limit request with the available size */
59 if (off + *len >= lim)
60 *len = lim - off;
61
62 if (!mtd_is_aligned_with_block_size(mtd, off)) {
63 printf("Offset not aligned with a block (0x%x)\n",
64 mtd->erasesize);
65 return 0;
66 }
67
68 /* first erase */
69 if (op == DFU_OP_WRITE) {
70 struct erase_info erase_op = {};
71
Sughosh Ganud6c907e2020-12-30 19:27:06 +053072 remaining = lock_len = round_up(*len, mtd->erasesize);
Patrick Delaunayf7aee232019-10-14 09:28:04 +020073 erase_op.mtd = mtd;
74 erase_op.addr = off;
75 erase_op.len = mtd->erasesize;
76 erase_op.scrub = 0;
77
Sughosh Ganud6c907e2020-12-30 19:27:06 +053078 debug("Unlocking the mtd device\n");
79 ret = mtd_unlock(mtd, lock_ofs, lock_len);
80 if (ret && ret != -EOPNOTSUPP) {
81 printf("MTD device unlock failed\n");
82 return 0;
83 }
84
Patrick Delaunayf7aee232019-10-14 09:28:04 +020085 while (remaining) {
86 if (erase_op.addr + remaining > lim) {
Patrick Delaunaye20cb052023-06-05 09:52:07 +020087 printf("Limit reached 0x%llx while erasing at offset 0x%llx, remaining 0x%llx\n",
88 lim, erase_op.addr, remaining);
Patrick Delaunayf7aee232019-10-14 09:28:04 +020089 return -EIO;
90 }
91
Patrick Delaunay4f99a202023-06-05 09:52:08 +020092 /* Skip the block if it is bad, don't erase it again */
93 ret = mtd_block_isbad(mtd, erase_op.addr);
94 if (ret) {
95 printf("Skipping %s at 0x%08llx\n",
96 ret == 1 ? "bad block" : "bbt reserved",
97 erase_op.addr);
98 erase_op.addr += mtd->erasesize;
99 continue;
100 }
101
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200102 ret = mtd_erase(mtd, &erase_op);
103
104 if (ret) {
Patrick Delaunay4f99a202023-06-05 09:52:08 +0200105 /* If this is not -EIO, we have no idea what to do. */
106 if (ret == -EIO) {
107 printf("Marking bad block at 0x%08llx (%d)\n",
108 erase_op.fail_addr, ret);
109 ret = mtd_block_markbad(mtd, erase_op.addr);
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200110 }
Patrick Delaunay4f99a202023-06-05 09:52:08 +0200111 /* Abort if it is not -EIO or can't mark bad */
112 if (ret) {
113 printf("Failure while erasing at offset 0x%llx (%d)\n",
114 erase_op.fail_addr, ret);
115 return ret;
116 }
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200117 } else {
118 remaining -= mtd->erasesize;
119 }
120
Patrick Delaunay4f99a202023-06-05 09:52:08 +0200121 /* Continue erase behind the current block */
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200122 erase_op.addr += mtd->erasesize;
123 }
124 }
125
126 io_op.mode = MTD_OPS_AUTO_OOB;
127 io_op.len = *len;
128 if (has_pages && io_op.len > mtd->writesize)
129 io_op.len = mtd->writesize;
130 io_op.ooblen = 0;
131 io_op.datbuf = buf;
132 io_op.oobbuf = NULL;
133
134 /* Loop over to do the actual read/write */
135 remaining = *len;
136 while (remaining) {
137 if (off + remaining > lim) {
138 printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
139 lim, op == DFU_OP_READ ? "reading" : "writing",
140 off);
141 if (op == DFU_OP_READ) {
142 *len -= remaining;
143 return 0;
144 } else {
145 return -EIO;
146 }
147 }
148
149 /* Skip the block if it is bad */
150 if (mtd_is_aligned_with_block_size(mtd, off) &&
151 mtd_block_isbad(mtd, off)) {
152 off += mtd->erasesize;
153 dfu->bad_skip += mtd->erasesize;
154 continue;
155 }
156
157 if (op == DFU_OP_READ)
158 ret = mtd_read_oob(mtd, off, &io_op);
Patrick Delaunayfd1f2632022-01-18 10:26:21 +0100159 else if (has_pages && dfu->data.mtd.ubi && mtd_page_is_empty(&io_op)) {
160 /* in case of ubi partition, do not write an empty page, only skip it */
161 ret = 0;
162 io_op.retlen = mtd->writesize;
163 io_op.oobretlen = mtd->oobsize;
164 } else {
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200165 ret = mtd_write_oob(mtd, off, &io_op);
Patrick Delaunayfd1f2632022-01-18 10:26:21 +0100166 }
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200167
168 if (ret) {
169 printf("Failure while %s at offset 0x%llx\n",
170 op == DFU_OP_READ ? "reading" : "writing", off);
171 return -EIO;
172 }
173
174 off += io_op.retlen;
175 remaining -= io_op.retlen;
176 io_op.datbuf += io_op.retlen;
177 io_op.len = remaining;
178 if (has_pages && io_op.len > mtd->writesize)
179 io_op.len = mtd->writesize;
180 }
181
Sughosh Ganud6c907e2020-12-30 19:27:06 +0530182 if (op == DFU_OP_WRITE) {
183 /* Write done, lock again */
184 debug("Locking the mtd device\n");
185 ret = mtd_lock(mtd, lock_ofs, lock_len);
Patrick Delaunay07c43fb2021-03-10 10:27:22 +0100186 if (ret == -EOPNOTSUPP)
187 ret = 0;
188 else if (ret)
Sughosh Ganud6c907e2020-12-30 19:27:06 +0530189 printf("MTD device lock failed\n");
190 }
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200191 return ret;
192}
193
194static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
195{
196 *size = dfu->data.mtd.info->size;
197
198 return 0;
199}
200
201static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
202 long *len)
203{
204 int ret = -1;
205
206 switch (dfu->layout) {
207 case DFU_RAW_ADDR:
208 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
209 break;
210 default:
211 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
212 dfu_get_layout(dfu->layout));
213 }
214
215 return ret;
216}
217
218static int dfu_write_medium_mtd(struct dfu_entity *dfu,
219 u64 offset, void *buf, long *len)
220{
221 int ret = -1;
222
223 switch (dfu->layout) {
224 case DFU_RAW_ADDR:
225 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
226 break;
227 default:
228 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
229 dfu_get_layout(dfu->layout));
230 }
231
232 return ret;
233}
234
235static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
236{
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200237 struct mtd_info *mtd = dfu->data.mtd.info;
238 u64 remaining;
239 int ret;
240
241 /* in case of ubi partition, erase rest of the partition */
Guillermo Rodriguezabbe8c42020-09-02 13:06:06 +0200242 if (dfu->data.mtd.ubi) {
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200243 struct erase_info erase_op = {};
244
245 erase_op.mtd = dfu->data.mtd.info;
246 erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
247 dfu->bad_skip, mtd->erasesize);
248 erase_op.len = mtd->erasesize;
249 erase_op.scrub = 0;
250
251 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
252 erase_op.addr;
253
254 while (remaining) {
255 ret = mtd_erase(mtd, &erase_op);
256
257 if (ret) {
258 /* Abort if its not a bad block error */
259 if (ret != -EIO)
260 break;
261 printf("Skipping bad block at 0x%08llx\n",
262 erase_op.addr);
263 }
264
265 /* Skip bad block and continue behind it */
266 erase_op.addr += mtd->erasesize;
267 remaining -= mtd->erasesize;
268 }
269 }
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200270 return 0;
271}
272
273static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
274{
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200275 /*
276 * Currently, Poll Timeout != 0 is only needed on nand
277 * ubi partition, as sectors which are not used need
278 * to be erased
279 */
Guillermo Rodriguezabbe8c42020-09-02 13:06:06 +0200280 if (dfu->data.mtd.ubi)
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200281 return DFU_MANIFEST_POLL_TIMEOUT;
282
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200283 return DFU_DEFAULT_POLL_TIMEOUT;
284}
285
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900286int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char **argv, int argc)
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200287{
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900288 char *s;
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200289 struct mtd_info *mtd;
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200290 int part;
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200291
292 mtd = get_mtd_device_nm(devstr);
293 if (IS_ERR_OR_NULL(mtd))
294 return -ENODEV;
295 put_mtd_device(mtd);
296
297 dfu->dev_type = DFU_DEV_MTD;
298 dfu->data.mtd.info = mtd;
Patrick Delaunay97f69af2021-03-04 17:47:56 +0100299 dfu->max_buf_size = mtd->erasesize;
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900300 if (argc < 1)
301 return -EINVAL;
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200302
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900303 if (!strcmp(argv[0], "raw")) {
304 if (argc != 3)
305 return -EINVAL;
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200306 dfu->layout = DFU_RAW_ADDR;
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900307 dfu->data.mtd.start = hextoul(argv[1], &s);
308 if (*s)
309 return -EINVAL;
310 dfu->data.mtd.size = hextoul(argv[2], &s);
311 if (*s)
312 return -EINVAL;
313 } else if ((!strcmp(argv[0], "part")) || (!strcmp(argv[0], "partubi"))) {
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200314 struct mtd_info *partition;
315 int partnum = 0;
316 bool part_found = false;
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200317
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900318 if (argc != 2)
319 return -EINVAL;
320
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200321 dfu->layout = DFU_RAW_ADDR;
322
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900323 part = dectoul(argv[1], &s);
324 if (*s)
325 return -EINVAL;
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200326
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200327 /* register partitions with MTDIDS/MTDPARTS or OF fallback */
328 mtd_probe_devices();
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200329
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200330 partnum = 0;
331 list_for_each_entry(partition, &mtd->partitions, node) {
332 partnum++;
333 if (partnum == part) {
334 part_found = true;
335 break;
336 }
337 }
338 if (!part_found) {
339 printf("No partition %d in %s\n", part, mtd->name);
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200340 return -1;
341 }
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200342 log_debug("partition %d:%s in %s\n", partnum, partition->name, mtd->name);
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200343
Patrick Delaunay9db77be2023-06-08 17:16:39 +0200344 dfu->data.mtd.start = partition->offset;
345 dfu->data.mtd.size = partition->size;
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900346 if (!strcmp(argv[0], "partubi"))
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200347 dfu->data.mtd.ubi = 1;
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200348 } else {
Masami Hiramatsufa4282a2022-01-31 11:52:37 +0900349 printf("%s: Memory layout (%s) not supported!\n", __func__, argv[0]);
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200350 return -1;
351 }
352
Patrick Delaunaycb47cb02019-10-14 09:28:05 +0200353 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
354 printf("Offset not aligned with a block (0x%x)\n",
355 mtd->erasesize);
356 return -EINVAL;
357 }
358 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
359 printf("Size not aligned with a block (0x%x)\n",
360 mtd->erasesize);
361 return -EINVAL;
362 }
363
Patrick Delaunayf7aee232019-10-14 09:28:04 +0200364 dfu->get_medium_size = dfu_get_medium_size_mtd;
365 dfu->read_medium = dfu_read_medium_mtd;
366 dfu->write_medium = dfu_write_medium_mtd;
367 dfu->flush_medium = dfu_flush_medium_mtd;
368 dfu->poll_timeout = dfu_polltimeout_mtd;
369
370 /* initial state */
371 dfu->inited = 0;
372
373 return 0;
374}