blob: 36cd4e945b2a5db57402a787cc7ab2c5b1be8839 [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
21static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu,
22 u64 offset, void *buf, long *len)
23{
24 u64 off, lim, remaining;
25 struct mtd_info *mtd = dfu->data.mtd.info;
26 struct mtd_oob_ops io_op = {};
27 int ret = 0;
28 bool has_pages = mtd->type == MTD_NANDFLASH ||
29 mtd->type == MTD_MLCNANDFLASH;
30
31 /* if buf == NULL return total size of the area */
32 if (!buf) {
33 *len = dfu->data.mtd.size;
34 return 0;
35 }
36
37 off = dfu->data.mtd.start + offset + dfu->bad_skip;
38 lim = dfu->data.mtd.start + dfu->data.mtd.size;
39
40 if (off >= lim) {
41 printf("Limit reached 0x%llx\n", lim);
42 *len = 0;
43 return op == DFU_OP_READ ? 0 : -EIO;
44 }
45 /* limit request with the available size */
46 if (off + *len >= lim)
47 *len = lim - off;
48
49 if (!mtd_is_aligned_with_block_size(mtd, off)) {
50 printf("Offset not aligned with a block (0x%x)\n",
51 mtd->erasesize);
52 return 0;
53 }
54
55 /* first erase */
56 if (op == DFU_OP_WRITE) {
57 struct erase_info erase_op = {};
58
59 remaining = round_up(*len, mtd->erasesize);
60 erase_op.mtd = mtd;
61 erase_op.addr = off;
62 erase_op.len = mtd->erasesize;
63 erase_op.scrub = 0;
64
65 while (remaining) {
66 if (erase_op.addr + remaining > lim) {
67 printf("Limit reached 0x%llx while erasing at offset 0x%llx\n",
68 lim, off);
69 return -EIO;
70 }
71
72 ret = mtd_erase(mtd, &erase_op);
73
74 if (ret) {
75 /* Abort if its not a bad block error */
76 if (ret != -EIO) {
77 printf("Failure while erasing at offset 0x%llx\n",
78 erase_op.fail_addr);
79 return 0;
80 }
81 printf("Skipping bad block at 0x%08llx\n",
82 erase_op.addr);
83 } else {
84 remaining -= mtd->erasesize;
85 }
86
87 /* Continue erase behind bad block */
88 erase_op.addr += mtd->erasesize;
89 }
90 }
91
92 io_op.mode = MTD_OPS_AUTO_OOB;
93 io_op.len = *len;
94 if (has_pages && io_op.len > mtd->writesize)
95 io_op.len = mtd->writesize;
96 io_op.ooblen = 0;
97 io_op.datbuf = buf;
98 io_op.oobbuf = NULL;
99
100 /* Loop over to do the actual read/write */
101 remaining = *len;
102 while (remaining) {
103 if (off + remaining > lim) {
104 printf("Limit reached 0x%llx while %s at offset 0x%llx\n",
105 lim, op == DFU_OP_READ ? "reading" : "writing",
106 off);
107 if (op == DFU_OP_READ) {
108 *len -= remaining;
109 return 0;
110 } else {
111 return -EIO;
112 }
113 }
114
115 /* Skip the block if it is bad */
116 if (mtd_is_aligned_with_block_size(mtd, off) &&
117 mtd_block_isbad(mtd, off)) {
118 off += mtd->erasesize;
119 dfu->bad_skip += mtd->erasesize;
120 continue;
121 }
122
123 if (op == DFU_OP_READ)
124 ret = mtd_read_oob(mtd, off, &io_op);
125 else
126 ret = mtd_write_oob(mtd, off, &io_op);
127
128 if (ret) {
129 printf("Failure while %s at offset 0x%llx\n",
130 op == DFU_OP_READ ? "reading" : "writing", off);
131 return -EIO;
132 }
133
134 off += io_op.retlen;
135 remaining -= io_op.retlen;
136 io_op.datbuf += io_op.retlen;
137 io_op.len = remaining;
138 if (has_pages && io_op.len > mtd->writesize)
139 io_op.len = mtd->writesize;
140 }
141
142 return ret;
143}
144
145static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size)
146{
147 *size = dfu->data.mtd.info->size;
148
149 return 0;
150}
151
152static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf,
153 long *len)
154{
155 int ret = -1;
156
157 switch (dfu->layout) {
158 case DFU_RAW_ADDR:
159 ret = mtd_block_op(DFU_OP_READ, dfu, offset, buf, len);
160 break;
161 default:
162 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
163 dfu_get_layout(dfu->layout));
164 }
165
166 return ret;
167}
168
169static int dfu_write_medium_mtd(struct dfu_entity *dfu,
170 u64 offset, void *buf, long *len)
171{
172 int ret = -1;
173
174 switch (dfu->layout) {
175 case DFU_RAW_ADDR:
176 ret = mtd_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
177 break;
178 default:
179 printf("%s: Layout (%s) not (yet) supported!\n", __func__,
180 dfu_get_layout(dfu->layout));
181 }
182
183 return ret;
184}
185
186static int dfu_flush_medium_mtd(struct dfu_entity *dfu)
187{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200188 struct mtd_info *mtd = dfu->data.mtd.info;
189 u64 remaining;
190 int ret;
191
192 /* in case of ubi partition, erase rest of the partition */
193 if (dfu->data.nand.ubi) {
194 struct erase_info erase_op = {};
195
196 erase_op.mtd = dfu->data.mtd.info;
197 erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset +
198 dfu->bad_skip, mtd->erasesize);
199 erase_op.len = mtd->erasesize;
200 erase_op.scrub = 0;
201
202 remaining = dfu->data.mtd.start + dfu->data.mtd.size -
203 erase_op.addr;
204
205 while (remaining) {
206 ret = mtd_erase(mtd, &erase_op);
207
208 if (ret) {
209 /* Abort if its not a bad block error */
210 if (ret != -EIO)
211 break;
212 printf("Skipping bad block at 0x%08llx\n",
213 erase_op.addr);
214 }
215
216 /* Skip bad block and continue behind it */
217 erase_op.addr += mtd->erasesize;
218 remaining -= mtd->erasesize;
219 }
220 }
Patrick Delaunay6015af22019-10-14 09:28:04 +0200221 return 0;
222}
223
224static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu)
225{
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200226 /*
227 * Currently, Poll Timeout != 0 is only needed on nand
228 * ubi partition, as sectors which are not used need
229 * to be erased
230 */
231 if (dfu->data.nand.ubi)
232 return DFU_MANIFEST_POLL_TIMEOUT;
233
Patrick Delaunay6015af22019-10-14 09:28:04 +0200234 return DFU_DEFAULT_POLL_TIMEOUT;
235}
236
237int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s)
238{
239 char *st;
240 struct mtd_info *mtd;
241 bool has_pages;
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200242 int ret, part;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200243
244 mtd = get_mtd_device_nm(devstr);
245 if (IS_ERR_OR_NULL(mtd))
246 return -ENODEV;
247 put_mtd_device(mtd);
248
249 dfu->dev_type = DFU_DEV_MTD;
250 dfu->data.mtd.info = mtd;
251
252 has_pages = mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH;
253 dfu->max_buf_size = has_pages ? mtd->erasesize : 0;
254
255 st = strsep(&s, " ");
256 if (!strcmp(st, "raw")) {
257 dfu->layout = DFU_RAW_ADDR;
258 dfu->data.mtd.start = simple_strtoul(s, &s, 16);
259 s++;
260 dfu->data.mtd.size = simple_strtoul(s, &s, 16);
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200261 } else if ((!strcmp(st, "part")) || (!strcmp(st, "partubi"))) {
262 char mtd_id[32];
263 struct mtd_device *mtd_dev;
264 u8 part_num;
265 struct part_info *pi;
266
267 dfu->layout = DFU_RAW_ADDR;
268
269 part = simple_strtoul(s, &s, 10);
270
271 sprintf(mtd_id, "%s,%d", devstr, part - 1);
272 printf("using id '%s'\n", mtd_id);
273
274 mtdparts_init();
275
276 ret = find_dev_and_part(mtd_id, &mtd_dev, &part_num, &pi);
277 if (ret != 0) {
278 printf("Could not locate '%s'\n", mtd_id);
279 return -1;
280 }
281
282 dfu->data.mtd.start = pi->offset;
283 dfu->data.mtd.size = pi->size;
284 if (!strcmp(st, "partubi"))
285 dfu->data.mtd.ubi = 1;
Patrick Delaunay6015af22019-10-14 09:28:04 +0200286 } else {
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200287 printf("%s: Memory layout (%s) not supported!\n", __func__, st);
Patrick Delaunay6015af22019-10-14 09:28:04 +0200288 return -1;
289 }
290
Patrick Delaunayd5640f72019-10-14 09:28:05 +0200291 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) {
292 printf("Offset not aligned with a block (0x%x)\n",
293 mtd->erasesize);
294 return -EINVAL;
295 }
296 if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) {
297 printf("Size not aligned with a block (0x%x)\n",
298 mtd->erasesize);
299 return -EINVAL;
300 }
301
Patrick Delaunay6015af22019-10-14 09:28:04 +0200302 dfu->get_medium_size = dfu_get_medium_size_mtd;
303 dfu->read_medium = dfu_read_medium_mtd;
304 dfu->write_medium = dfu_write_medium_mtd;
305 dfu->flush_medium = dfu_flush_medium_mtd;
306 dfu->poll_timeout = dfu_polltimeout_mtd;
307
308 /* initial state */
309 dfu->inited = 0;
310
311 return 0;
312}