blob: cce9ce0845ee45986399520ae09f5a4a5056dc93 [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>
Patrick Delaunayd5640f72019-10-14 09:28:05 +020013#include <jffs2/load_kernel.h>
Simon Glass61b29b82020-02-03 07:36:15 -070014#include <linux/err.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) {
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);
Patrick Delaunay56227472022-01-18 10:26:21 +0100146 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 {
Patrick Delaunay6015af22019-10-14 09:28:04 +0200152 ret = mtd_write_oob(mtd, off, &io_op);
Patrick Delaunay56227472022-01-18 10:26:21 +0100153 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200154
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
Sughosh Ganu65f3fc12020-12-30 19:27:06 +0530169 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);
Patrick Delaunaya5bb3842021-03-10 10:27:22 +0100173 if (ret == -EOPNOTSUPP)
174 ret = 0;
175 else if (ret)
Sughosh Ganu65f3fc12020-12-30 19:27:06 +0530176 printf("MTD device lock failed\n");
177 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200178 return ret;
179}
180
181static 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
188static 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
205static 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
222static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
223{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200224 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 */
Guillermo Rodriguez1b3c4cb2020-09-02 13:06:06 +0200229 if (dfu->data.mtd.ubi) {
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200230 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 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200257 return 0;
258}
259
260static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
261{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200262 /*
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 */
Guillermo Rodriguez1b3c4cb2020-09-02 13:06:06 +0200267 if (dfu->data.mtd.ubi)
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200268 return DFU_MANIFEST_POLL_TIMEOUT;
269
Patrick Delaunay6015af22019-10-14 09:28:04 +0200270 return DFU_DEFAULT_POLL_TIMEOUT;
271}
272
273int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s)
274{
275 char *st;
276 struct mtd_info *mtd;
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200277 int ret, part;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200278
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;
Patrick Delaunay2dc41fc2021-03-04 17:47:56 +0100286 dfu->max_buf_size = mtd->erasesize;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200287
288 st = strsep(&s, " ");
289 if (!strcmp(st, "raw")) {
290 dfu->layout = DFU_RAW_ADDR;
Simon Glass7e5f4602021-07-24 09:03:29 -0600291 dfu->data.mtd.start = hextoul(s, &s);
Patrick Delaunay6015af22019-10-14 09:28:04 +0200292 s++;
Simon Glass7e5f4602021-07-24 09:03:29 -0600293 dfu->data.mtd.size = hextoul(s, &s);
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200294 } else if ((!strcmp(st, "part")) || (!strcmp(st, "partubi"))) {
295 char mtd_id[32];
296 struct mtd_device *mtd_dev;
297 u8 part_num;
298 struct part_info *pi;
299
300 dfu->layout = DFU_RAW_ADDR;
301
Simon Glass0b1284e2021-07-24 09:03:30 -0600302 part = dectoul(s, &s);
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200303
304 sprintf(mtd_id, "%s,%d", devstr, part - 1);
305 printf("using id '%s'\n", mtd_id);
306
307 mtdparts_init();
308
309 ret = find_dev_and_part(mtd_id, &mtd_dev, &part_num, &pi);
310 if (ret != 0) {
311 printf("Could not locate '%s'\n", mtd_id);
312 return -1;
313 }
314
315 dfu->data.mtd.start = pi->offset;
316 dfu->data.mtd.size = pi->size;
317 if (!strcmp(st, "partubi"))
318 dfu->data.mtd.ubi = 1;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200319 } else {
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200320 printf("%s: Memory layout (%s) not supported!\n", __func__, st);
Patrick Delaunay6015af22019-10-14 09:28:04 +0200321 return -1;
322 }
323
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200324 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
325 printf("Offset not aligned with a block (0x%x)\n",
326 mtd->erasesize);
327 return -EINVAL;
328 }
329 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
330 printf("Size not aligned with a block (0x%x)\n",
331 mtd->erasesize);
332 return -EINVAL;
333 }
334
Patrick Delaunay6015af22019-10-14 09:28:04 +0200335 dfu->get_medium_size = dfu_get_medium_size_mtd;
336 dfu->read_medium = dfu_read_medium_mtd;
337 dfu->write_medium = dfu_write_medium_mtd;
338 dfu->flush_medium = dfu_flush_medium_mtd;
339 dfu->poll_timeout = dfu_polltimeout_mtd;
340
341 /* initial state */
342 dfu->inited = 0;
343
344 return 0;
345}