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