Prepare v2023.10
[platform/kernel/u-boot.git] / drivers / dfu / dfu_mtd.c
1 // 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>
13 #include <linux/err.h>
14 #include <linux/ctype.h>
15
16 static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
17 {
18         return !do_div(size, mtd->erasesize);
19 }
20
21 /* Logic taken from cmd/mtd.c:mtd_oob_write_is_empty() */
22 static 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
35 static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
36                         u64 offset, void *buf, long *len)
37 {
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 = {};
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
51         off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip;
52         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
73                 remaining = lock_len = round_up(*len, mtd->erasesize);
74                 erase_op.mtd = mtd;
75                 erase_op.addr = off;
76                 erase_op.len = mtd->erasesize;
77                 erase_op.scrub = 0;
78
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");
83                         return 0;
84                 }
85
86                 while (remaining) {
87                         if (erase_op.addr + remaining > lim) {
88                                 printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
89                                        lim, off);
90                                 return -EIO;
91                         }
92
93                         ret = mtd_erase(mtd, &erase_op);
94
95                         if (ret) {
96                                 /* Abort if its not a bad block error */
97                                 if (ret != -EIO) {
98                                         printf("Failure while erasing at offset 0x%llx\n",
99                                                erase_op.fail_addr);
100                                         return 0;
101                                 }
102                                 printf("Skipping bad block at 0x%08llx\n",
103                                        erase_op.addr);
104                         } else {
105                                 remaining -= mtd->erasesize;
106                         }
107
108                         /* Continue erase behind bad block */
109                         erase_op.addr += mtd->erasesize;
110                 }
111         }
112
113         io_op.mode = MTD_OPS_AUTO_OOB;
114         io_op.len = *len;
115         if (has_pages && io_op.len > mtd->writesize)
116                 io_op.len = mtd->writesize;
117         io_op.ooblen = 0;
118         io_op.datbuf = buf;
119         io_op.oobbuf = NULL;
120
121         /* Loop over to do the actual read/write */
122         remaining = *len;
123         while (remaining) {
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",
127                                off);
128                         if (op == DFU_OP_READ) {
129                                 *len -= remaining;
130                                 return 0;
131                         } else {
132                                 return -EIO;
133                         }
134                 }
135
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;
141                         continue;
142                 }
143
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 */
148                         ret = 0;
149                         io_op.retlen = mtd->writesize;
150                         io_op.oobretlen = mtd->oobsize;
151                 } else {
152                         ret = mtd_write_oob(mtd, off, &io_op);
153                 }
154
155                 if (ret) {
156                         printf("Failure while %s at offset 0x%llx\n",
157                                op == DFU_OP_READ ? "reading" : "writing", off);
158                         return -EIO;
159                 }
160
161                 off += io_op.retlen;
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;
167         }
168
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)
174                         ret = 0;
175                 else if (ret)
176                         printf("MTD device lock failed\n");
177         }
178         return ret;
179 }
180
181 static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
182 {
183         *size = dfu->data.mtd.info->size;
184
185         return 0;
186 }
187
188 static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
189                                long *len)
190 {
191         int ret = -1;
192
193         switch (dfu->layout) {
194         case DFU_RAW_ADDR:
195                 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
196                 break;
197         default:
198                 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
199                        dfu_get_layout(dfu->layout));
200         }
201
202         return ret;
203 }
204
205 static int dfu_write_medium_mtd(struct dfu_entity *dfu,
206                                 u64 offset, void *buf, long *len)
207 {
208         int ret = -1;
209
210         switch (dfu->layout) {
211         case DFU_RAW_ADDR:
212                 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
213                 break;
214         default:
215                 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
216                        dfu_get_layout(dfu->layout));
217         }
218
219         return ret;
220 }
221
222 static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
223 {
224         struct mtd_info *mtd = dfu->data.mtd.info;
225         u64 remaining;
226         int ret;
227
228         /* in case of ubi partition, erase rest of the partition */
229         if (dfu->data.mtd.ubi) {
230                 struct erase_info erase_op = {};
231
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;
236                 erase_op.scrub = 0;
237
238                 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
239                             erase_op.addr;
240
241                 while (remaining) {
242                         ret = mtd_erase(mtd, &erase_op);
243
244                         if (ret) {
245                                 /* Abort if its not a bad block error */
246                                 if (ret != -EIO)
247                                         break;
248                                 printf("Skipping bad block at 0x%08llx\n",
249                                        erase_op.addr);
250                         }
251
252                         /* Skip bad block and continue behind it */
253                         erase_op.addr += mtd->erasesize;
254                         remaining -= mtd->erasesize;
255                 }
256         }
257         return 0;
258 }
259
260 static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
261 {
262         /*
263          * Currently, Poll Timeout != 0 is only needed on nand
264          * ubi partition, as sectors which are not used need
265          * to be erased
266          */
267         if (dfu->data.mtd.ubi)
268                 return DFU_MANIFEST_POLL_TIMEOUT;
269
270         return DFU_DEFAULT_POLL_TIMEOUT;
271 }
272
273 int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char **argv, int argc)
274 {
275         char *s;
276         struct mtd_info *mtd;
277         int part;
278
279         mtd = get_mtd_device_nm(devstr);
280         if (IS_ERR_OR_NULL(mtd))
281                 return -ENODEV;
282         put_mtd_device(mtd);
283
284         dfu->dev_type = DFU_DEV_MTD;
285         dfu->data.mtd.info = mtd;
286         dfu->max_buf_size = mtd->erasesize;
287         if (argc < 1)
288                 return -EINVAL;
289
290         if (!strcmp(argv[0], "raw")) {
291                 if (argc != 3)
292                         return -EINVAL;
293                 dfu->layout = DFU_RAW_ADDR;
294                 dfu->data.mtd.start = hextoul(argv[1], &s);
295                 if (*s)
296                         return -EINVAL;
297                 dfu->data.mtd.size = hextoul(argv[2], &s);
298                 if (*s)
299                         return -EINVAL;
300         } else if ((!strcmp(argv[0], "part")) || (!strcmp(argv[0], "partubi"))) {
301                 struct mtd_info *partition;
302                 int partnum = 0;
303                 bool part_found = false;
304
305                 if (argc != 2)
306                         return -EINVAL;
307
308                 dfu->layout = DFU_RAW_ADDR;
309
310                 part = dectoul(argv[1], &s);
311                 if (*s)
312                         return -EINVAL;
313
314                 /* register partitions with MTDIDS/MTDPARTS or OF fallback */
315                 mtd_probe_devices();
316
317                 partnum = 0;
318                 list_for_each_entry(partition, &mtd->partitions, node) {
319                         partnum++;
320                         if (partnum == part) {
321                                 part_found = true;
322                                 break;
323                         }
324                 }
325                 if (!part_found) {
326                         printf("No partition %d in %s\n", part, mtd->name);
327                         return -1;
328                 }
329                 log_debug("partition %d:%s in %s\n", partnum, partition->name, mtd->name);
330
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;
335         } else {
336                 printf("%s: Memory layout (%s) not supported!\n", __func__, argv[0]);
337                 return -1;
338         }
339
340         if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
341                 printf("Offset not aligned with a block (0x%x)\n",
342                        mtd->erasesize);
343                 return -EINVAL;
344         }
345         if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
346                 printf("Size not aligned with a block (0x%x)\n",
347                        mtd->erasesize);
348                 return -EINVAL;
349         }
350
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;
356
357         /* initial state */
358         dfu->inited = 0;
359
360         return 0;
361 }