]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
f22b11c1 ŁM |
2 | /* |
3 | * dfu.c -- DFU back-end routines | |
4 | * | |
5 | * Copyright (C) 2012 Samsung Electronics | |
6 | * author: Lukasz Majewski <l.majewski@samsung.com> | |
f22b11c1 ŁM |
7 | */ |
8 | ||
d678a59d | 9 | #include <common.h> |
7b51b576 | 10 | #include <env.h> |
e7e75c70 | 11 | #include <errno.h> |
f7ae49fc | 12 | #include <log.h> |
f22b11c1 ŁM |
13 | #include <malloc.h> |
14 | #include <mmc.h> | |
15 | #include <fat.h> | |
16 | #include <dfu.h> | |
bd694244 | 17 | #include <hash.h> |
f22b11c1 ŁM |
18 | #include <linux/list.h> |
19 | #include <linux/compiler.h> | |
1e94b46f | 20 | #include <linux/printk.h> |
f22b11c1 | 21 | |
6beaa47d | 22 | LIST_HEAD(dfu_list); |
f22b11c1 | 23 | static int dfu_alt_num; |
b627eb46 | 24 | static int alt_num_cnt; |
bd694244 | 25 | static struct hash_algo *dfu_hash_algo; |
98a8f445 AS |
26 | #ifdef CONFIG_DFU_TIMEOUT |
27 | static unsigned long dfu_timeout = 0; | |
28 | #endif | |
f22b11c1 | 29 | |
c533f94c MS |
30 | bool dfu_reinit_needed = false; |
31 | ||
067c13c7 PD |
32 | /* |
33 | * The purpose of the dfu_flush_callback() function is to | |
34 | * provide callback for dfu user | |
35 | */ | |
36 | __weak void dfu_flush_callback(struct dfu_entity *dfu) | |
37 | { | |
38 | } | |
39 | ||
40 | /* | |
41 | * The purpose of the dfu_initiated_callback() function is to | |
42 | * provide callback for dfu user | |
43 | */ | |
44 | __weak void dfu_initiated_callback(struct dfu_entity *dfu) | |
45 | { | |
46 | } | |
47 | ||
d4710326 PD |
48 | /* |
49 | * The purpose of the dfu_error_callback() function is to | |
50 | * provide callback for dfu user | |
51 | */ | |
52 | __weak void dfu_error_callback(struct dfu_entity *dfu, const char *msg) | |
53 | { | |
54 | } | |
55 | ||
1cc03c5c ŁM |
56 | /* |
57 | * The purpose of the dfu_usb_get_reset() function is to | |
58 | * provide information if after USB_DETACH request | |
59 | * being sent the dfu-util performed reset of USB | |
60 | * bus. | |
61 | * | |
62 | * Described behaviour is the only way to distinct if | |
63 | * user has typed -e (detach) or -R (reset) when invoking | |
64 | * dfu-util command. | |
65 | * | |
66 | */ | |
67 | __weak bool dfu_usb_get_reset(void) | |
68 | { | |
66928afb R |
69 | #ifdef CONFIG_SPL_DFU_NO_RESET |
70 | return false; | |
71 | #else | |
1cc03c5c | 72 | return true; |
66928afb | 73 | #endif |
1cc03c5c ŁM |
74 | } |
75 | ||
98a8f445 AS |
76 | #ifdef CONFIG_DFU_TIMEOUT |
77 | void dfu_set_timeout(unsigned long timeout) | |
78 | { | |
79 | dfu_timeout = timeout; | |
80 | } | |
81 | ||
82 | unsigned long dfu_get_timeout(void) | |
83 | { | |
84 | return dfu_timeout; | |
85 | } | |
86 | #endif | |
87 | ||
f22b11c1 ŁM |
88 | static int dfu_find_alt_num(const char *s) |
89 | { | |
90 | int i = 0; | |
91 | ||
92 | for (; *s; s++) | |
93 | if (*s == ';') | |
94 | i++; | |
95 | ||
96 | return ++i; | |
97 | } | |
98 | ||
febabe3e PD |
99 | /* |
100 | * treat dfu_alt_info with several interface information | |
101 | * to allow DFU on several device with one command, | |
102 | * the string format is | |
103 | * interface devstring'='alternate list (';' separated) | |
104 | * and each interface separated by '&' | |
105 | */ | |
106 | int dfu_config_interfaces(char *env) | |
107 | { | |
108 | struct dfu_entity *dfu; | |
109 | char *s, *i, *d, *a, *part; | |
110 | int ret = -EINVAL; | |
111 | int n = 1; | |
112 | ||
113 | s = env; | |
114 | for (; *s; s++) { | |
115 | if (*s == ';') | |
116 | n++; | |
117 | if (*s == '&') | |
118 | n++; | |
119 | } | |
120 | ret = dfu_alt_init(n, &dfu); | |
121 | if (ret) | |
122 | return ret; | |
123 | ||
124 | s = env; | |
125 | while (s) { | |
126 | ret = -EINVAL; | |
8db74c15 | 127 | i = strsep(&s, " \t"); |
febabe3e PD |
128 | if (!i) |
129 | break; | |
8db74c15 | 130 | s = skip_spaces(s); |
febabe3e PD |
131 | d = strsep(&s, "="); |
132 | if (!d) | |
133 | break; | |
134 | a = strsep(&s, "&"); | |
135 | if (!a) | |
136 | a = s; | |
137 | do { | |
138 | part = strsep(&a, ";"); | |
de06083c | 139 | part = skip_spaces(part); |
febabe3e PD |
140 | ret = dfu_alt_add(dfu, i, d, part); |
141 | if (ret) | |
142 | return ret; | |
143 | } while (a); | |
144 | } | |
145 | ||
146 | return ret; | |
147 | } | |
148 | ||
dd64827e | 149 | int dfu_init_env_entities(char *interface, char *devstr) |
765c5ae5 ŁM |
150 | { |
151 | const char *str_env; | |
152 | char *env_bkp; | |
87a8ca98 | 153 | int ret = 0; |
765c5ae5 | 154 | |
c533f94c MS |
155 | dfu_reinit_needed = false; |
156 | ||
899a5282 PM |
157 | #ifdef CONFIG_SET_DFU_ALT_INFO |
158 | set_dfu_alt_info(interface, devstr); | |
159 | #endif | |
00caae6d | 160 | str_env = env_get("dfu_alt_info"); |
765c5ae5 | 161 | if (!str_env) { |
9b643e31 | 162 | pr_err("\"dfu_alt_info\" env variable not defined!\n"); |
765c5ae5 ŁM |
163 | return -EINVAL; |
164 | } | |
165 | ||
166 | env_bkp = strdup(str_env); | |
febabe3e PD |
167 | if (!interface && !devstr) |
168 | ret = dfu_config_interfaces(env_bkp); | |
169 | else | |
170 | ret = dfu_config_entities(env_bkp, interface, devstr); | |
171 | ||
765c5ae5 | 172 | if (ret) { |
9b643e31 | 173 | pr_err("DFU entities configuration failed!\n"); |
28a5c880 | 174 | pr_err("(partition table does not match dfu_alt_info?)\n"); |
87a8ca98 | 175 | goto done; |
765c5ae5 ŁM |
176 | } |
177 | ||
87a8ca98 | 178 | done: |
765c5ae5 | 179 | free(env_bkp); |
87a8ca98 | 180 | return ret; |
765c5ae5 ŁM |
181 | } |
182 | ||
e7e75c70 | 183 | static unsigned char *dfu_buf; |
a7e6892f | 184 | static unsigned long dfu_buf_size; |
febabe3e | 185 | static enum dfu_device_type dfu_buf_device_type; |
e7e75c70 | 186 | |
d4278263 | 187 | unsigned char *dfu_free_buf(void) |
e7e75c70 HS |
188 | { |
189 | free(dfu_buf); | |
190 | dfu_buf = NULL; | |
191 | return dfu_buf; | |
192 | } | |
193 | ||
4fb12789 ŁM |
194 | unsigned long dfu_get_buf_size(void) |
195 | { | |
196 | return dfu_buf_size; | |
197 | } | |
198 | ||
7ac1b410 | 199 | unsigned char *dfu_get_buf(struct dfu_entity *dfu) |
e7e75c70 HS |
200 | { |
201 | char *s; | |
202 | ||
febabe3e PD |
203 | /* manage several entity with several contraint */ |
204 | if (dfu_buf && dfu->dev_type != dfu_buf_device_type) | |
205 | dfu_free_buf(); | |
206 | ||
e7e75c70 HS |
207 | if (dfu_buf != NULL) |
208 | return dfu_buf; | |
209 | ||
00caae6d | 210 | s = env_get("dfu_bufsiz"); |
f597fc3d PM |
211 | if (s) |
212 | dfu_buf_size = (unsigned long)simple_strtol(s, NULL, 0); | |
213 | ||
214 | if (!s || !dfu_buf_size) | |
215 | dfu_buf_size = CONFIG_SYS_DFU_DATA_BUF_SIZE; | |
216 | ||
7ac1b410 SW |
217 | if (dfu->max_buf_size && dfu_buf_size > dfu->max_buf_size) |
218 | dfu_buf_size = dfu->max_buf_size; | |
e7e75c70 HS |
219 | |
220 | dfu_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, dfu_buf_size); | |
221 | if (dfu_buf == NULL) | |
222 | printf("%s: Could not memalign 0x%lx bytes\n", | |
223 | __func__, dfu_buf_size); | |
224 | ||
febabe3e | 225 | dfu_buf_device_type = dfu->dev_type; |
e7e75c70 HS |
226 | return dfu_buf; |
227 | } | |
f22b11c1 | 228 | |
bd694244 ŁM |
229 | static char *dfu_get_hash_algo(void) |
230 | { | |
231 | char *s; | |
232 | ||
00caae6d | 233 | s = env_get("dfu_hash_algo"); |
3d83e675 | 234 | if (!s) |
bd694244 ŁM |
235 | return NULL; |
236 | ||
3d83e675 ŁM |
237 | if (!strcmp(s, "crc32")) { |
238 | debug("%s: DFU hash method: %s\n", __func__, s); | |
239 | return s; | |
240 | } | |
241 | ||
9b643e31 | 242 | pr_err("DFU hash method: %s not supported!\n", s); |
bd694244 ŁM |
243 | return NULL; |
244 | } | |
245 | ||
ea2453d5 PA |
246 | static int dfu_write_buffer_drain(struct dfu_entity *dfu) |
247 | { | |
248 | long w_size; | |
249 | int ret; | |
250 | ||
251 | /* flush size? */ | |
252 | w_size = dfu->i_buf - dfu->i_buf_start; | |
253 | if (w_size == 0) | |
254 | return 0; | |
255 | ||
bd694244 ŁM |
256 | if (dfu_hash_algo) |
257 | dfu_hash_algo->hash_update(dfu_hash_algo, &dfu->crc, | |
258 | dfu->i_buf_start, w_size, 0); | |
ea2453d5 PA |
259 | |
260 | ret = dfu->write_medium(dfu, dfu->offset, dfu->i_buf_start, &w_size); | |
261 | if (ret) | |
262 | debug("%s: Write error!\n", __func__); | |
263 | ||
264 | /* point back */ | |
265 | dfu->i_buf = dfu->i_buf_start; | |
266 | ||
267 | /* update offset */ | |
268 | dfu->offset += w_size; | |
269 | ||
270 | puts("#"); | |
271 | ||
272 | return ret; | |
273 | } | |
274 | ||
57da0607 | 275 | void dfu_transaction_cleanup(struct dfu_entity *dfu) |
3ee9593f SW |
276 | { |
277 | /* clear everything */ | |
3ee9593f SW |
278 | dfu->crc = 0; |
279 | dfu->offset = 0; | |
280 | dfu->i_blk_seq_num = 0; | |
6fa8dddd PD |
281 | dfu->i_buf_start = dfu_get_buf(dfu); |
282 | dfu->i_buf_end = dfu->i_buf_start; | |
3ee9593f | 283 | dfu->i_buf = dfu->i_buf_start; |
57da0607 PD |
284 | dfu->r_left = 0; |
285 | dfu->b_left = 0; | |
286 | dfu->bad_skip = 0; | |
287 | ||
3ee9593f SW |
288 | dfu->inited = 0; |
289 | } | |
290 | ||
6fa8dddd PD |
291 | int dfu_transaction_initiate(struct dfu_entity *dfu, bool read) |
292 | { | |
293 | int ret = 0; | |
294 | ||
295 | if (dfu->inited) | |
296 | return 0; | |
297 | ||
298 | dfu_transaction_cleanup(dfu); | |
299 | ||
300 | if (dfu->i_buf_start == NULL) | |
301 | return -ENOMEM; | |
302 | ||
303 | dfu->i_buf_end = dfu->i_buf_start + dfu_get_buf_size(); | |
304 | ||
305 | if (read) { | |
306 | ret = dfu->get_medium_size(dfu, &dfu->r_left); | |
307 | if (ret < 0) | |
308 | return ret; | |
309 | debug("%s: %s %lld [B]\n", __func__, dfu->name, dfu->r_left); | |
310 | } | |
311 | ||
312 | dfu->inited = 1; | |
067c13c7 | 313 | dfu_initiated_callback(dfu); |
6fa8dddd PD |
314 | |
315 | return 0; | |
316 | } | |
317 | ||
a2199afe HS |
318 | int dfu_flush(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) |
319 | { | |
320 | int ret = 0; | |
321 | ||
1aa4bdc8 PM |
322 | ret = dfu_write_buffer_drain(dfu); |
323 | if (ret) | |
324 | return ret; | |
325 | ||
a2199afe HS |
326 | if (dfu->flush_medium) |
327 | ret = dfu->flush_medium(dfu); | |
328 | ||
bd694244 ŁM |
329 | if (dfu_hash_algo) |
330 | printf("\nDFU complete %s: 0x%08x\n", dfu_hash_algo->name, | |
331 | dfu->crc); | |
a2199afe | 332 | |
067c13c7 PD |
333 | dfu_flush_callback(dfu); |
334 | ||
57da0607 | 335 | dfu_transaction_cleanup(dfu); |
a2199afe HS |
336 | |
337 | return ret; | |
338 | } | |
339 | ||
f22b11c1 ŁM |
340 | int dfu_write(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) |
341 | { | |
3ee9593f | 342 | int ret; |
f22b11c1 | 343 | |
e621c7ab | 344 | debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x offset: 0x%llx bufoffset: 0x%lx\n", |
ea2453d5 | 345 | __func__, dfu->name, buf, size, blk_seq_num, dfu->offset, |
e621c7ab | 346 | (unsigned long)(dfu->i_buf - dfu->i_buf_start)); |
f22b11c1 | 347 | |
6fa8dddd PD |
348 | ret = dfu_transaction_initiate(dfu, false); |
349 | if (ret < 0) | |
350 | return ret; | |
f22b11c1 | 351 | |
ea2453d5 | 352 | if (dfu->i_blk_seq_num != blk_seq_num) { |
f22b11c1 | 353 | printf("%s: Wrong sequence number! [%d] [%d]\n", |
ea2453d5 | 354 | __func__, dfu->i_blk_seq_num, blk_seq_num); |
57da0607 | 355 | dfu_transaction_cleanup(dfu); |
d4710326 | 356 | dfu_error_callback(dfu, "Wrong sequence number"); |
f22b11c1 ŁM |
357 | return -1; |
358 | } | |
359 | ||
ea2453d5 PA |
360 | /* DFU 1.1 standard says: |
361 | * The wBlockNum field is a block sequence number. It increments each | |
362 | * time a block is transferred, wrapping to zero from 65,535. It is used | |
363 | * to provide useful context to the DFU loader in the device." | |
364 | * | |
365 | * This means that it's a 16 bit counter that roll-overs at | |
366 | * 0xffff -> 0x0000. By having a typical 4K transfer block | |
367 | * we roll-over at exactly 256MB. Not very fun to debug. | |
368 | * | |
369 | * Handling rollover, and having an inited variable, | |
370 | * makes things work. | |
371 | */ | |
372 | ||
373 | /* handle rollover */ | |
374 | dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff; | |
375 | ||
376 | /* flush buffer if overflow */ | |
377 | if ((dfu->i_buf + size) > dfu->i_buf_end) { | |
3ee9593f SW |
378 | ret = dfu_write_buffer_drain(dfu); |
379 | if (ret) { | |
57da0607 | 380 | dfu_transaction_cleanup(dfu); |
d4710326 | 381 | dfu_error_callback(dfu, "DFU write error"); |
3ee9593f SW |
382 | return ret; |
383 | } | |
ea2453d5 | 384 | } |
f22b11c1 | 385 | |
ea2453d5 PA |
386 | /* we should be in buffer now (if not then size too large) */ |
387 | if ((dfu->i_buf + size) > dfu->i_buf_end) { | |
9b643e31 | 388 | pr_err("Buffer overflow! (0x%p + 0x%x > 0x%p)\n", dfu->i_buf, |
a7d2c3cd | 389 | size, dfu->i_buf_end); |
57da0607 | 390 | dfu_transaction_cleanup(dfu); |
d4710326 | 391 | dfu_error_callback(dfu, "Buffer overflow"); |
ea2453d5 PA |
392 | return -1; |
393 | } | |
394 | ||
395 | memcpy(dfu->i_buf, buf, size); | |
396 | dfu->i_buf += size; | |
397 | ||
398 | /* if end or if buffer full flush */ | |
399 | if (size == 0 || (dfu->i_buf + size) > dfu->i_buf_end) { | |
3ee9593f SW |
400 | ret = dfu_write_buffer_drain(dfu); |
401 | if (ret) { | |
57da0607 | 402 | dfu_transaction_cleanup(dfu); |
d4710326 | 403 | dfu_error_callback(dfu, "DFU write error"); |
3ee9593f SW |
404 | return ret; |
405 | } | |
ea2453d5 PA |
406 | } |
407 | ||
3ee9593f | 408 | return 0; |
ea2453d5 PA |
409 | } |
410 | ||
411 | static int dfu_read_buffer_fill(struct dfu_entity *dfu, void *buf, int size) | |
412 | { | |
413 | long chunk; | |
414 | int ret, readn; | |
415 | ||
416 | readn = 0; | |
417 | while (size > 0) { | |
418 | /* get chunk that can be read */ | |
b4141195 | 419 | chunk = min((long)size, dfu->b_left); |
ea2453d5 PA |
420 | /* consume */ |
421 | if (chunk > 0) { | |
422 | memcpy(buf, dfu->i_buf, chunk); | |
bd694244 ŁM |
423 | if (dfu_hash_algo) |
424 | dfu_hash_algo->hash_update(dfu_hash_algo, | |
425 | &dfu->crc, buf, | |
426 | chunk, 0); | |
427 | ||
ea2453d5 PA |
428 | dfu->i_buf += chunk; |
429 | dfu->b_left -= chunk; | |
430 | size -= chunk; | |
431 | buf += chunk; | |
432 | readn += chunk; | |
433 | } | |
434 | ||
435 | /* all done */ | |
436 | if (size > 0) { | |
437 | /* no more to read */ | |
438 | if (dfu->r_left == 0) | |
439 | break; | |
440 | ||
441 | dfu->i_buf = dfu->i_buf_start; | |
442 | dfu->b_left = dfu->i_buf_end - dfu->i_buf_start; | |
443 | ||
444 | /* got to read, but buffer is empty */ | |
445 | if (dfu->b_left > dfu->r_left) | |
446 | dfu->b_left = dfu->r_left; | |
447 | ret = dfu->read_medium(dfu, dfu->offset, dfu->i_buf, | |
448 | &dfu->b_left); | |
449 | if (ret != 0) { | |
450 | debug("%s: Read error!\n", __func__); | |
451 | return ret; | |
452 | } | |
0de1022d PD |
453 | if (dfu->b_left == 0) |
454 | break; | |
ea2453d5 PA |
455 | dfu->offset += dfu->b_left; |
456 | dfu->r_left -= dfu->b_left; | |
457 | ||
458 | puts("#"); | |
459 | } | |
460 | } | |
461 | ||
462 | return readn; | |
f22b11c1 ŁM |
463 | } |
464 | ||
465 | int dfu_read(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) | |
466 | { | |
f22b11c1 ŁM |
467 | int ret = 0; |
468 | ||
469 | debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x i_buf: 0x%p\n", | |
ea2453d5 PA |
470 | __func__, dfu->name, buf, size, blk_seq_num, dfu->i_buf); |
471 | ||
6fa8dddd PD |
472 | ret = dfu_transaction_initiate(dfu, true); |
473 | if (ret < 0) | |
474 | return ret; | |
f22b11c1 | 475 | |
ea2453d5 | 476 | if (dfu->i_blk_seq_num != blk_seq_num) { |
f22b11c1 | 477 | printf("%s: Wrong sequence number! [%d] [%d]\n", |
ea2453d5 | 478 | __func__, dfu->i_blk_seq_num, blk_seq_num); |
f22b11c1 ŁM |
479 | return -1; |
480 | } | |
ea2453d5 PA |
481 | /* handle rollover */ |
482 | dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff; | |
f22b11c1 | 483 | |
ea2453d5 PA |
484 | ret = dfu_read_buffer_fill(dfu, buf, size); |
485 | if (ret < 0) { | |
486 | printf("%s: Failed to fill buffer\n", __func__); | |
487 | return -1; | |
f22b11c1 | 488 | } |
ea2453d5 PA |
489 | |
490 | if (ret < size) { | |
bd694244 ŁM |
491 | if (dfu_hash_algo) |
492 | debug("%s: %s %s: 0x%x\n", __func__, dfu->name, | |
493 | dfu_hash_algo->name, dfu->crc); | |
ea2453d5 PA |
494 | puts("\nUPLOAD ... done\nCtrl+C to exit ...\n"); |
495 | ||
57da0607 | 496 | dfu_transaction_cleanup(dfu); |
ea2453d5 PA |
497 | } |
498 | ||
f22b11c1 ŁM |
499 | return ret; |
500 | } | |
501 | ||
502 | static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt, | |
dd64827e | 503 | char *interface, char *devstr) |
f22b11c1 | 504 | { |
53b40636 MH |
505 | char *argv[DFU_MAX_ENTITY_ARGS]; |
506 | int argc; | |
f22b11c1 ŁM |
507 | char *st; |
508 | ||
dd64827e | 509 | debug("%s: %s interface: %s dev: %s\n", __func__, s, interface, devstr); |
8db74c15 | 510 | st = strsep(&s, " \t"); |
d8ae90a8 | 511 | strlcpy(dfu->name, st, DFU_NAME_SIZE); |
53b40636 MH |
512 | |
513 | /* Parse arguments */ | |
514 | for (argc = 0; s && argc < DFU_MAX_ENTITY_ARGS; argc++) { | |
515 | s = skip_spaces(s); | |
516 | if (!*s) | |
517 | break; | |
518 | argv[argc] = strsep(&s, " \t"); | |
519 | } | |
520 | ||
521 | if (argc == DFU_MAX_ENTITY_ARGS && s) { | |
522 | s = skip_spaces(s); | |
523 | if (*s) { | |
524 | log_err("Too many arguments for %s\n", dfu->name); | |
525 | return -EINVAL; | |
526 | } | |
527 | } | |
f22b11c1 | 528 | |
f22b11c1 | 529 | dfu->alt = alt; |
7ac1b410 | 530 | dfu->max_buf_size = 0; |
cb7bd2e0 | 531 | dfu->free_entity = NULL; |
f22b11c1 ŁM |
532 | |
533 | /* Specific for mmc device */ | |
534 | if (strcmp(interface, "mmc") == 0) { | |
53b40636 | 535 | if (dfu_fill_entity_mmc(dfu, devstr, argv, argc)) |
f22b11c1 | 536 | return -1; |
6015af28 | 537 | } else if (strcmp(interface, "mtd") == 0) { |
53b40636 | 538 | if (dfu_fill_entity_mtd(dfu, devstr, argv, argc)) |
6015af28 | 539 | return -1; |
c6631764 | 540 | } else if (strcmp(interface, "nand") == 0) { |
53b40636 | 541 | if (dfu_fill_entity_nand(dfu, devstr, argv, argc)) |
c6631764 | 542 | return -1; |
a9479f04 | 543 | } else if (strcmp(interface, "ram") == 0) { |
53b40636 | 544 | if (dfu_fill_entity_ram(dfu, devstr, argv, argc)) |
a9479f04 | 545 | return -1; |
6f12ebf6 | 546 | } else if (strcmp(interface, "sf") == 0) { |
53b40636 | 547 | if (dfu_fill_entity_sf(dfu, devstr, argv, argc)) |
6f12ebf6 | 548 | return -1; |
ec44cace | 549 | } else if (strcmp(interface, "virt") == 0) { |
53b40636 | 550 | if (dfu_fill_entity_virt(dfu, devstr, argv, argc)) |
ec44cace | 551 | return -1; |
f22b11c1 ŁM |
552 | } else { |
553 | printf("%s: Device %s not (yet) supported!\n", | |
554 | __func__, interface); | |
555 | return -1; | |
556 | } | |
806bd245 | 557 | dfu_get_buf(dfu); |
f22b11c1 ŁM |
558 | |
559 | return 0; | |
560 | } | |
561 | ||
562 | void dfu_free_entities(void) | |
563 | { | |
564 | struct dfu_entity *dfu, *p, *t = NULL; | |
565 | ||
806bd245 | 566 | dfu_free_buf(); |
f22b11c1 ŁM |
567 | list_for_each_entry_safe_reverse(dfu, p, &dfu_list, list) { |
568 | list_del(&dfu->list); | |
cb7bd2e0 SW |
569 | if (dfu->free_entity) |
570 | dfu->free_entity(dfu); | |
f22b11c1 ŁM |
571 | t = dfu; |
572 | } | |
573 | if (t) | |
574 | free(t); | |
575 | INIT_LIST_HEAD(&dfu_list); | |
b627eb46 PM |
576 | |
577 | alt_num_cnt = 0; | |
f22b11c1 ŁM |
578 | } |
579 | ||
9ada6830 | 580 | int dfu_alt_init(int num, struct dfu_entity **dfu) |
f22b11c1 | 581 | { |
f22b11c1 | 582 | char *s; |
9ada6830 | 583 | int ret; |
f22b11c1 | 584 | |
9ada6830 | 585 | dfu_alt_num = num; |
f22b11c1 ŁM |
586 | debug("%s: dfu_alt_num=%d\n", __func__, dfu_alt_num); |
587 | ||
bd694244 ŁM |
588 | dfu_hash_algo = NULL; |
589 | s = dfu_get_hash_algo(); | |
590 | if (s) { | |
591 | ret = hash_lookup_algo(s, &dfu_hash_algo); | |
592 | if (ret) | |
9b643e31 | 593 | pr_err("Hash algorithm %s not supported\n", s); |
bd694244 ŁM |
594 | } |
595 | ||
9ada6830 PD |
596 | *dfu = calloc(sizeof(struct dfu_entity), dfu_alt_num); |
597 | if (!*dfu) | |
598 | return -1; | |
599 | ||
600 | return 0; | |
601 | } | |
602 | ||
603 | int dfu_alt_add(struct dfu_entity *dfu, char *interface, char *devstr, char *s) | |
604 | { | |
605 | struct dfu_entity *p_dfu; | |
606 | int ret; | |
607 | ||
608 | if (alt_num_cnt >= dfu_alt_num) | |
609 | return -1; | |
610 | ||
611 | p_dfu = &dfu[alt_num_cnt]; | |
612 | ret = dfu_fill_entity(p_dfu, s, alt_num_cnt, interface, devstr); | |
613 | if (ret) | |
f22b11c1 | 614 | return -1; |
f22b11c1 | 615 | |
9ada6830 PD |
616 | list_add_tail(&p_dfu->list, &dfu_list); |
617 | alt_num_cnt++; | |
618 | ||
619 | return 0; | |
620 | } | |
621 | ||
622 | int dfu_config_entities(char *env, char *interface, char *devstr) | |
623 | { | |
624 | struct dfu_entity *dfu; | |
625 | int i, ret; | |
626 | char *s; | |
627 | ||
628 | ret = dfu_alt_init(dfu_find_alt_num(env), &dfu); | |
629 | if (ret) | |
630 | return -1; | |
631 | ||
632 | for (i = 0; i < dfu_alt_num; i++) { | |
f22b11c1 | 633 | s = strsep(&env, ";"); |
de06083c | 634 | s = skip_spaces(s); |
9ada6830 | 635 | ret = dfu_alt_add(dfu, interface, devstr, s); |
5d8fae79 | 636 | if (ret) { |
feaa7856 | 637 | /* We will free "dfu" in dfu_free_entities() */ |
f22b11c1 | 638 | return -1; |
5d8fae79 | 639 | } |
f22b11c1 ŁM |
640 | } |
641 | ||
642 | return 0; | |
643 | } | |
644 | ||
645 | const char *dfu_get_dev_type(enum dfu_device_type t) | |
646 | { | |
909b690b | 647 | const char *const dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM", |
ec44cace | 648 | "SF", "MTD", "VIRT"}; |
f22b11c1 ŁM |
649 | return dev_t[t]; |
650 | } | |
651 | ||
652 | const char *dfu_get_layout(enum dfu_layout l) | |
653 | { | |
909b690b | 654 | const char *const dfu_layout[] = {NULL, "RAW_ADDR", "FAT", "EXT2", |
c533f94c MS |
655 | "EXT3", "EXT4", "RAM_ADDR", "SKIP", |
656 | "SCRIPT" }; | |
f22b11c1 ŁM |
657 | return dfu_layout[l]; |
658 | } | |
659 | ||
660 | void dfu_show_entities(void) | |
661 | { | |
662 | struct dfu_entity *dfu; | |
663 | ||
664 | puts("DFU alt settings list:\n"); | |
665 | ||
666 | list_for_each_entry(dfu, &dfu_list, list) { | |
667 | printf("dev: %s alt: %d name: %s layout: %s\n", | |
668 | dfu_get_dev_type(dfu->dev_type), dfu->alt, | |
669 | dfu->name, dfu_get_layout(dfu->layout)); | |
670 | } | |
671 | } | |
672 | ||
673 | int dfu_get_alt_number(void) | |
674 | { | |
675 | return dfu_alt_num; | |
676 | } | |
677 | ||
678 | struct dfu_entity *dfu_get_entity(int alt) | |
679 | { | |
680 | struct dfu_entity *dfu; | |
681 | ||
682 | list_for_each_entry(dfu, &dfu_list, list) { | |
683 | if (dfu->alt == alt) | |
684 | return dfu; | |
685 | } | |
686 | ||
687 | return NULL; | |
688 | } | |
fed936ed ŁM |
689 | |
690 | int dfu_get_alt(char *name) | |
691 | { | |
692 | struct dfu_entity *dfu; | |
5610b057 | 693 | char *str; |
fed936ed ŁM |
694 | |
695 | list_for_each_entry(dfu, &dfu_list, list) { | |
5610b057 ŁM |
696 | if (dfu->name[0] != '/') { |
697 | if (!strncmp(dfu->name, name, strlen(dfu->name))) | |
698 | return dfu->alt; | |
699 | } else { | |
700 | /* | |
701 | * One must also consider absolute path | |
702 | * (/boot/bin/uImage) available at dfu->name when | |
703 | * compared "plain" file name (uImage) | |
704 | * | |
705 | * It is the case for e.g. thor gadget where lthor SW | |
706 | * sends only the file name, so only the very last part | |
707 | * of path must be checked for equality | |
708 | */ | |
709 | ||
710 | str = strstr(dfu->name, name); | |
711 | if (!str) | |
712 | continue; | |
713 | ||
714 | /* | |
715 | * Check if matching substring is the last element of | |
716 | * dfu->name (uImage) | |
717 | */ | |
718 | if (strlen(dfu->name) == | |
719 | ((str - dfu->name) + strlen(name))) | |
720 | return dfu->alt; | |
721 | } | |
fed936ed ŁM |
722 | } |
723 | ||
724 | return -ENODEV; | |
725 | } | |
2092e461 LM |
726 | |
727 | int dfu_write_from_mem_addr(struct dfu_entity *dfu, void *buf, int size) | |
728 | { | |
729 | unsigned long dfu_buf_size, write, left = size; | |
730 | int i, ret = 0; | |
731 | void *dp = buf; | |
732 | ||
733 | /* | |
734 | * Here we must call dfu_get_buf(dfu) first to be sure that dfu_buf_size | |
735 | * has been properly initialized - e.g. if "dfu_bufsiz" has been taken | |
736 | * into account. | |
737 | */ | |
738 | dfu_get_buf(dfu); | |
739 | dfu_buf_size = dfu_get_buf_size(); | |
740 | debug("%s: dfu buf size: %lu\n", __func__, dfu_buf_size); | |
741 | ||
742 | for (i = 0; left > 0; i++) { | |
743 | write = min(dfu_buf_size, left); | |
744 | ||
745 | debug("%s: dp: 0x%p left: %lu write: %lu\n", __func__, | |
746 | dp, left, write); | |
747 | ret = dfu_write(dfu, dp, write, i); | |
748 | if (ret) { | |
9b643e31 | 749 | pr_err("DFU write failed\n"); |
2092e461 LM |
750 | return ret; |
751 | } | |
752 | ||
753 | dp += write; | |
754 | left -= write; | |
755 | } | |
756 | ||
757 | ret = dfu_flush(dfu, NULL, 0, i); | |
758 | if (ret) | |
9b643e31 | 759 | pr_err("DFU flush failed!"); |
f8d7e6e9 | 760 | puts("\n"); |
2092e461 LM |
761 | |
762 | return ret; | |
763 | } |