1 /*
2 * (C) Copyright 2021 Rockchip Electronics Co., Ltd.
3 *
4 * SPDX-License-Identifier: GPL-2.0+
5 */
6
7 #include <common.h>
8 #include <boot_rkimg.h>
9 #include <crypto.h>
10 #include <dm.h>
11 #include <sysmem.h>
12 #include <u-boot/sha256.h>
13 #ifdef CONFIG_ANDROID_AB
14 #include <android_avb/avb_ops_user.h>
15 #include <android_avb/rk_avb_ops_user.h>
16 #endif
17 #include <asm/arch/vendor.h>
18
19 DECLARE_GLOBAL_DATA_PTR;
20
21 #define TFTPUD_I(fmt, args...) printf("[TFTPUD]: "fmt, ##args)
22 #define TFTPUD_E(fmt, args...) printf("[TFTPUD-ERROR]: "fmt, ##args)
23
24 #define UPDATE_HDR_FILE "update.hdr"
25 #define GPT_ENV_FILE "gpt_env.txt"
26 #define MAX_UPDATE_HEADER_SIZE SZ_128K
27 #define MAX_REMAIN_TRIES 3
28 #define SHA256_HASH_SIZE 32
29
30 struct update_header {
31 struct list_head images;
32 void *shared_buf;
33 u32 version;
34 u32 rollback_idx;
35 u32 lba_step;
36 u32 mb;
37 int force_update;
38 const char *spec_partition;
39 };
40
41 struct local_information {
42 u32 version;
43 u32 rollback_idx;
44 char current_slot[3];
45 };
46
47 struct image_element {
48 char file_name[32];
49 char part_name[32];
50 void *buf;
51 u32 size; /* uint: byte */
52 u32 lba_start;
53 u32 lba_offset;
54 u32 lba_cnt;
55 u8 remain_tries;
56 int hash_noffset;
57 struct list_head node;
58 };
59
60 static struct update_header update_hdr;
61 static struct local_information local_info;
62 static const char *server_dir;
63
tftpfw_version_set(u32 version)64 static int tftpfw_version_set(u32 version)
65 {
66 int ret;
67
68 ret = vendor_storage_write(FIRMWARE_VER_ID, &version, sizeof(version));
69
70 return ret < 0 ? ret : 0;
71 }
72
tftpfw_version_get(void)73 static u32 tftpfw_version_get(void)
74 {
75 u32 version;
76 int ret;
77
78 ret = vendor_storage_read(FIRMWARE_VER_ID, &version, sizeof(version));
79 if (ret < 0) {
80 if (ret == -EINVAL) {
81 version = 0; /* first initial as 0 */
82 TFTPUD_I("Initial firmware version as 0\n");
83 ret = tftpfw_version_set(version);
84 if (ret < 0)
85 return ret;
86 } else {
87 return ret;
88 }
89 }
90
91 return version;
92 }
93
tftp_download(void * addr,const char * file)94 static int tftp_download(void *addr, const char *file)
95 {
96 char tftp_cmd[64];
97
98 if (server_dir)
99 snprintf(tftp_cmd, 64, "tftp 0x%lx %s/%s",
100 (ulong)addr, server_dir, file);
101 else
102 snprintf(tftp_cmd, 64, "tftp 0x%lx %s", (ulong)addr, file);
103
104 return run_command(tftp_cmd, 0);
105 }
106
update_cleanup(void * fit,struct update_header * hdr)107 static void update_cleanup(void *fit, struct update_header *hdr)
108 {
109 struct image_element *e;
110 struct list_head *node;
111
112 list_for_each(node, &hdr->images) {
113 e = list_entry(node, struct image_element, node);
114 free(e);
115 }
116
117 if (hdr->shared_buf)
118 free(hdr->shared_buf);
119 if (fit)
120 free(fit);
121 }
122
is_gpt(const char * name)123 static inline int is_gpt(const char *name)
124 {
125 if (!name)
126 return 0;
127
128 return !strcmp(name, GPT_ENV_FILE);
129 }
130
update_populate_image(void * fit,struct update_header * hdr)131 static int update_populate_image(void *fit, struct update_header *hdr)
132 {
133 struct blk_desc *dev_desc;
134 struct image_element *e;
135 disk_partition_t part;
136 const char *name, *dp;
137 const char *noseq_name;
138 char *last_part_name = NULL;
139 uint last_lba_offset = 0;
140 uint lba_offset;
141 int images, noffset;
142 int ret;
143
144 images = fdt_path_offset(fit, FIT_IMAGES_PATH);
145 if (images < 0)
146 return images;
147
148 dev_desc = rockchip_get_bootdev();
149 if (!dev_desc)
150 return -ENODEV;
151
152 fdt_for_each_subnode(noffset, fit, images) {
153 name = fit_get_name(fit, noffset, NULL);
154 printf("# %s:\n", name);
155
156 if (is_gpt(name))
157 continue;
158
159 e = malloc(sizeof(*e));
160 if (!e)
161 return -ENOMEM;
162
163 e->remain_tries = MAX_REMAIN_TRIES;
164 e->buf = hdr->shared_buf;
165 e->size = fdtdec_get_uint(fit, noffset, "data-size", -ENODATA);
166 if (e->size == -ENODATA)
167 return -ENODATA;
168
169 /* part name */
170 strcpy(e->file_name, name);
171 strcat(e->file_name, ".part.img");
172 noseq_name = strstr(name, "-");
173 if (!noseq_name)
174 return -EINVAL;
175 noseq_name++;
176 dp = strstr(noseq_name, "-");
177 if (!dp)
178 return -EINVAL;
179 dp++;
180 strlcpy(e->part_name, noseq_name, strlen(noseq_name) - strlen(dp));
181 ret = part_get_info_by_name_strict(dev_desc, e->part_name, &part);
182 if (ret < 0) {
183 TFTPUD_E("No partition '%s'\n", e->part_name);
184 return -EINVAL;
185 }
186
187 /* lba */
188 if (!strcmp(last_part_name, e->part_name))
189 lba_offset = last_lba_offset + hdr->lba_step;
190 else
191 lba_offset = 0;
192
193 e->lba_start = part.start;
194 e->lba_offset = lba_offset;
195 e->lba_cnt = DIV_ROUND_UP(e->size, 512);
196 e->hash_noffset = fdt_subnode_offset(fit, noffset, "hash");
197 if (e->hash_noffset < 0)
198 return e->hash_noffset;
199
200 list_add_tail(&e->node, &hdr->images);
201 last_part_name = e->part_name;
202 last_lba_offset = lba_offset;
203
204 printf(" file: %s\n", e->file_name);
205 printf(" partition: %s\n", e->part_name);
206 printf(" buf: 0x%08lx\n", (ulong)e->buf);
207 printf(" size: 0x%08x\n", e->size);
208 printf(" lba_start: 0x%08x\n", e->lba_start);
209 printf(" lba_offset: 0x%08x\n", e->lba_offset);
210 printf(" lba_cnt: 0x%08x\n", e->lba_cnt);
211 printf(" remain_tries: %d\n", e->remain_tries);
212 printf(" hash_noffset: 0x%08x\n\n", e->hash_noffset);
213 }
214
215 return 0;
216 }
217
update_download_hdr(struct update_header * hdr)218 static void *update_download_hdr(struct update_header *hdr)
219 {
220 u32 filesz;
221 void *fit;
222
223 fit = memalign(ARCH_DMA_MINALIGN, MAX_UPDATE_HEADER_SIZE);
224 if (!fit)
225 return NULL;
226
227 if (tftp_download(fit, UPDATE_HDR_FILE)) {
228 free(fit);
229 return NULL;
230 }
231
232 if (fdt_check_header(fit)) {
233 TFTPUD_E("invalid update hdr magic\n");
234 free(fit);
235 return NULL;
236 }
237
238 /* sha256 csum was appended at the end of update.hdr */
239 filesz = env_get_ulong("filesize", 16, 0);
240 if ((fdt_totalsize(fit) + SHA256_HASH_SIZE) != filesz) {
241 TFTPUD_E("invalid sha256 hash at the tail of hdr\n");
242 return NULL;
243 }
244
245 return fit;
246 }
247
248 #ifndef CONFIG_FIT_SIGNATURE
249 #ifdef CONFIG_DM_CRYPTO
sha256_checksum(char * input,u32 input_len,u8 * output)250 static void sha256_checksum(char *input, u32 input_len, u8 *output)
251 {
252 sha_context csha_ctx;
253 struct udevice *dev;
254
255 dev = crypto_get_device(CRYPTO_SHA256);
256 if (!dev) {
257 TFTPUD_E("No crypto device\n");
258 return;
259 }
260 csha_ctx.algo = CRYPTO_SHA256;
261 csha_ctx.length = input_len;
262 crypto_sha_csum(dev, &csha_ctx, (char *)input, input_len, output);
263 }
264 #else
sha256_checksum(char * input,u32 input_len,u8 * output)265 static void sha256_checksum(char *input, u32 input_len, u8 *output)
266 {
267 sha256_csum((const uchar *)input, input_len, output);
268 }
269 #endif
270
hdr_checksum_verify(void * fit,struct update_header * hdr)271 static int hdr_checksum_verify(void *fit, struct update_header *hdr)
272 {
273 u8 *hash, csum[SHA256_HASH_SIZE];
274 int ret, i;
275
276 hash = (u8 *)fit + fdt_totalsize(fit);
277 sha256_checksum(fit, fdt_totalsize(fit), csum);
278 ret = memcmp(hash, csum, SHA256_HASH_SIZE) ? -EINVAL : 0;
279 if (ret) {
280 printf(" update.hash: ");
281 for (i = 0; i < SHA256_HASH_SIZE; i++)
282 printf("%02x", hash[i]);
283 printf("\n");
284
285 printf(" calculate hash: ");
286 for (i = 0; i < SHA256_HASH_SIZE; i++)
287 printf("%02x", csum[i]);
288 printf("\n");
289 }
290
291 return ret;
292 }
293 #endif
294
print_hdr_local(struct update_header * hdr,struct local_information * local)295 static void print_hdr_local(struct update_header *hdr,
296 struct local_information *local)
297 {
298 printf("# Server:\n");
299 printf(" version: %d\n", hdr->version);
300 printf(" rollback_idx: %d\n", hdr->rollback_idx);
301 printf(" force_update: %d\n", hdr->force_update);
302 printf(" MB: %d\n", hdr->mb);
303 printf(" lba_step: 0x%08x\n", hdr->lba_step);
304 printf(" shared_buf: 0x%08lx - 0x%08lx\n",
305 (ulong)hdr->shared_buf, (ulong)hdr->shared_buf + hdr->mb * SZ_1M);
306 printf(" spec_partition: %s\n\n", hdr->spec_partition);
307
308 printf("# Local:\n");
309 printf(" version: %d\n", local->version);
310 printf(" rollback_idx: %d\n", local->rollback_idx);
311 printf(" current_slot: %s\n", local->current_slot);
312 printf("\n");
313 }
314
hdr_param_verify(void * fit,struct update_header * hdr,struct local_information * local,int conf)315 static int hdr_param_verify(void *fit, struct update_header *hdr,
316 struct local_information *local, int conf)
317 {
318 u32 size;
319 int ret;
320
321 /* remote */
322 hdr->version = fdtdec_get_uint(fit, 0, "version", 0);
323 hdr->rollback_idx = fdtdec_get_uint(fit, conf, "rollback-index", 0);
324 hdr->force_update = fdtdec_get_uint(fit, conf, "force_update", 0);
325 hdr->mb = fdtdec_get_uint(fit, conf, "image-size-MB", 0);
326 size = hdr->mb * SZ_1M;
327 hdr->lba_step = size / 512;
328 /* TODO: use sysmem alloc/free */
329 hdr->shared_buf = malloc(size);
330 if (!hdr->shared_buf)
331 return -ENOMEM;
332
333 /* local */
334 ret = tftpfw_version_get();
335 if (ret < 0) {
336 TFTPUD_E("Failed to get local firmware version, ret=%d\n", ret);
337 return local->version;
338 }
339 local->version = ret;
340 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
341 u32 remote_rollback_idx;
342
343 ret = fit_rollback_index_verify(fit, FIT_ROLLBACK_INDEX,
344 &remote_rollback_idx, &local->rollback_idx);
345 if (ret) {
346 TFTPUD_E("Failed to get local rollback-index, ret=%d\n", ret);
347 return ret;
348 }
349 #else
350 local->rollback_idx = -1;
351 #endif
352 #ifdef CONFIG_ANDROID_AB
353 ret = rk_avb_get_current_slot(local->current_slot);
354 if (ret) {
355 TFTPUD_E("Failed to get local current slot, ret=%d\n", ret);
356 return ret;
357 }
358 #else
359 strcpy(local->current_slot, "-");
360 #endif
361
362 print_hdr_local(hdr, local);
363
364 /* verify */
365 if (hdr->force_update) {
366 TFTPUD_I("Remote requires force upgrade !\n");
367 return 0;
368 }
369 if (hdr->version < local->version) {
370 TFTPUD_E("Invalid firmware version: %d(remote) < %d(local)\n",
371 hdr->version, local->version);
372 return -EINVAL;
373 }
374 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
375 if (remote_rollback_idx < local->rollback_idx) {
376 TFTPUD_E("Invalid rollback-index: %d(remote) < %d(local)\n",
377 remote_rollback_idx, local->rollback_idx);
378 return -EINVAL;
379 }
380 #endif
381
382 return 0;
383 }
384
update_verify_hdr(void * fit,struct update_header * hdr,struct local_information * local)385 static int update_verify_hdr(void *fit, struct update_header *hdr,
386 struct local_information *local)
387 {
388 const char *name;
389 int noffset;
390 int conf;
391 int ret;
392
393 noffset = fdt_path_offset(fit, FIT_CONFS_PATH);
394 name = fdt_getprop(fit, noffset, "default", NULL);
395 conf = fdt_subnode_offset(fit, noffset, name);
396 if (conf < 0)
397 return conf;
398
399 #ifdef CONFIG_FIT_SIGNATURE
400 /* Secure: verify signature */
401 ret = fit_config_verify(fit, conf);
402 if (ret)
403 return ret;
404
405 TFTPUD_I("hdr signature verified\n");
406 #else
407 /* Non-secure: verify hash */
408 ret = hdr_checksum_verify(fit, hdr);
409 if (ret)
410 return ret;
411
412 TFTPUD_I("hdr checksum verified\n");
413 #endif
414 /* verify rollback index ..., etc */
415 ret = hdr_param_verify(fit, hdr, local, conf);
416 if (ret)
417 return ret;
418
419 TFTPUD_I("hdr param verified\n");
420
421 return 0;
422 }
423
update_local_info(void * fit,struct update_header * hdr)424 static int update_local_info(void *fit, struct update_header *hdr)
425 {
426 int ret;
427
428 TFTPUD_I("Update local information... ");
429
430 ret = tftpfw_version_set(hdr->version);
431 if (ret) {
432 TFTPUD_E("Update local param FAIL, ret=%d\n", ret);
433 return ret;
434 }
435 printf("fw_version=%d ", hdr->version);
436
437 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
438 ret = fit_write_trusty_rollback_index(hdr->rollback_idx);
439 if (ret)
440 return ret;
441 printf("rollback_idx=%d ", hdr->rollback_idx);
442 #endif
443 printf("\n");
444
445 return 0;
446 }
447
update_ignore_image(void * fit,struct update_header * hdr,struct image_element * e)448 static int update_ignore_image(void *fit, struct update_header *hdr,
449 struct image_element *e)
450 {
451 #ifdef CONFIG_ANDROID_AB
452 char *slot_suffix;
453
454 /* Android A/B skip current slot */
455 slot_suffix = (char *)e->part_name + strlen(e->part_name) - 2;
456 if (!strcmp(hdr->current_slot, slot_suffix))
457 return 1;
458 #endif
459 /* try to find expected target partition */
460 if (hdr->spec_partition && strcmp(e->part_name, hdr->spec_partition))
461 return 1;
462
463 return 0;
464 }
465
download_image(void * fit,struct image_element * e)466 static int download_image(void *fit, struct image_element *e)
467 {
468 ulong fileaddr;
469 ulong filesize;
470 char *msg = "";
471 int ret;
472
473 /* download */
474 printf("[TFTPUD-0]: download \"%s\" at 0x%lx\n",
475 e->file_name, (ulong)e->buf);
476
477 ret = tftp_download(e->buf, e->file_name);
478 if (ret)
479 return ret;
480
481 fileaddr = env_get_ulong("fileaddr", 16, 0);
482 filesize = env_get_ulong("filesize", 16, 0);
483 if (!fileaddr || !filesize) {
484 TFTPUD_E("No fileaddr and filesize\n");
485 return -ENOENT;
486 }
487
488 if (filesize != e->size) {
489 TFTPUD_E("Expected filesize 0x%08lx != 0x%08x\n", filesize, e->size);
490 return -EINVAL;
491 }
492
493 /* verify */
494 printf("[TFTPUD-1]: verify ");
495 ret = fit_image_check_hash(fit, e->hash_noffset, e->buf, e->size, &msg);
496 printf("[%s]\n", ret ? "-" : "+");
497
498 return ret;
499 }
500
update_flash_image(struct image_element * e)501 static int update_flash_image(struct image_element *e)
502 {
503 struct blk_desc *dev_desc;
504 int ret;
505
506 dev_desc = rockchip_get_bootdev();
507 if (!dev_desc) {
508 TFTPUD_E("No boot device\n");
509 return -ENODEV;
510 }
511
512 printf("[TFTPUD-2]: Flash to \"%s\" partition at LBA offset 0x%08x, "
513 "with 0x%08x sectors ... ",
514 e->part_name, e->lba_offset, e->lba_cnt);
515
516 if (dev_desc->if_type == IF_TYPE_MTD) {
517 dev_desc->op_flag |= BLK_MTD_CONT_WRITE;
518 ret = blk_dwrite(dev_desc, e->lba_start + e->lba_offset,
519 e->lba_cnt, (void *)e->buf);
520 dev_desc->op_flag &= ~(BLK_MTD_CONT_WRITE);
521 } else {
522 ret = blk_dwrite(dev_desc, e->lba_start + e->lba_offset,
523 e->lba_cnt, (void *)e->buf);
524 }
525
526 if (ret != e->lba_cnt)
527 printf("Failed(%d)\n\n\n", ret);
528 else
529 printf("OK\n\n\n");
530
531 return 0;
532 }
533
update_download_image(void * fit,struct image_element * e)534 static int update_download_image(void *fit, struct image_element *e)
535 {
536 int i, ret;
537
538 for (i = 0; i < e->remain_tries; i++) {
539 ret = download_image(fit, e);
540 if (!ret)
541 return 0;
542
543 TFTPUD_E("retry-%d download\n", i);
544 continue;
545 }
546
547 return -ENODATA;
548 }
549
update_write_gpt(void * fit,struct update_header * hdr)550 static int update_write_gpt(void *fit, struct update_header *hdr)
551 {
552 struct image_element *e;
553 char *gpt_parts, *p;
554 const char *name;
555 int images;
556 int noffset;
557 int ret = 0;
558
559 images = fdt_path_offset(fit, FIT_IMAGES_PATH);
560 if (images < 0)
561 return images;
562
563 noffset = fdt_first_subnode(fit, images);
564 if (noffset < 0)
565 return noffset;
566
567 /* gpt must be the 1st node */
568 name = fit_get_name(fit, noffset, NULL);
569 if (!is_gpt(name))
570 return 0;
571
572 e = malloc(sizeof(*e));
573 if (!e)
574 return -ENOMEM;
575
576 e->remain_tries = MAX_REMAIN_TRIES;
577 e->buf = hdr->shared_buf;
578 e->size = fdtdec_get_uint(fit, noffset, "data-size", -ENODATA);
579 if (e->size == -ENODATA) {
580 ret = -EINVAL;
581 goto out;
582 }
583
584 strcpy(e->file_name, name);
585 e->hash_noffset = fdt_subnode_offset(fit, noffset, "hash");
586 if (e->hash_noffset < 0)
587 return e->hash_noffset;
588
589 printf("\n# %s:\n", e->file_name);
590 printf(" buf: 0x%08lx\n", (ulong)e->buf);
591 printf(" size: 0x%08x\n", e->size);
592 printf(" remain_tries: %d\n", e->remain_tries);
593 printf(" hash_noffset: 0x%08x\n\n", e->hash_noffset);
594
595 /* download */
596 ret = update_download_image(fit, e);
597 if (ret) {
598 TFTPUD_E("\"%s\" download fail, ret=%d\n",
599 e->file_name, ret);
600 goto out;
601 }
602
603 /* terminate gpt string */
604 gpt_parts = (char *)e->buf;
605 p = gpt_parts + e->size - 1;
606 *p = '\0';
607
608 /* write */
609 printf("[TFTPUD-2]: Write gpt ...\n");
610 printf(" %s\n\n", gpt_parts);
611 env_set("gpt_parts", gpt_parts);
612 ret = run_command("gpt write ${devtype} ${devnum} ${gpt_parts}", 0);
613 if (ret) {
614 printf("Failed to write gpt\n");
615 ret = -EIO;
616 goto out;
617 }
618 ret = run_command("gpt verify ${devtype} ${devnum} ${gpt_parts}", 0);
619 if (ret) {
620 printf("Failed to verify gpt\n");
621 ret = -EIO;
622 goto out;
623 }
624 printf("\n");
625 out:
626 free(e);
627
628 return ret;
629 }
630
do_tftp_full_update(cmd_tbl_t * cmdtp,int flag,int argc,char * const argv[])631 static int do_tftp_full_update(cmd_tbl_t *cmdtp, int flag,
632 int argc, char * const argv[])
633 {
634 struct local_information *local = &local_info;
635 struct update_header *hdr = &update_hdr;
636 struct image_element *e;
637 struct list_head *node;
638 const char *dir_part_str;
639 const char *part_str;
640 const char *dir_str;
641 char *dup_str = NULL;
642 u32 total_success = 0;
643 u32 total_traverse = 0;
644 ulong start_ms;
645 ulong total_ms;
646 void *fit;
647 int ret;
648
649 start_ms = get_timer(0);
650 memset(hdr, 0, sizeof(*hdr));
651 memset(local, 0, sizeof(*local));
652
653 /* only handle a single partititon ? */
654 if (argc > 1) {
655 dir_part_str = argv[1];
656 part_str = strchr(dir_part_str, ':');
657 if (part_str) {
658 /*
659 * eg: tftpupdate image:recovery
660 * tftpupdate image:*
661 * tftpupdate image:
662 */
663 dup_str = strdup(dir_part_str);
664 dup_str[part_str - dir_part_str] = 0;
665 dir_str = dup_str;
666 part_str++;
667 if (*part_str == '*')
668 part_str = NULL;
669 } else {
670 /* eg: tftpupdate recovery */
671 dir_str = NULL;
672 part_str = argv[1];
673 }
674 } else {
675 dir_str = NULL;
676 part_str = NULL;
677 }
678
679 server_dir = dir_str;
680 hdr->spec_partition = part_str;
681 INIT_LIST_HEAD(&hdr->images);
682
683 fit = update_download_hdr(hdr);
684 if (!fit) {
685 TFTPUD_E("download hdr fail\n");
686 ret = -EINVAL;
687 goto out;
688 }
689
690 ret = update_verify_hdr(fit, hdr, local);
691 if (ret) {
692 TFTPUD_E("verify hdr fail, ret=%d\n", ret);
693 goto out;
694 }
695
696 /* flash gpt table early than any other partition */
697 ret = update_write_gpt(fit, hdr);
698 if (ret) {
699 TFTPUD_E("write gpt fail, ret=%d\n", ret);
700 goto out;
701 }
702
703 ret = update_populate_image(fit, hdr);
704 if (ret) {
705 TFTPUD_E("populate image fail, ret=%d\n", ret);
706 goto out;
707 }
708
709 list_for_each(node, &hdr->images) {
710 e = list_entry(node, struct image_element, node);
711 total_traverse++;
712
713 /* ignore ? */
714 if (update_ignore_image(fit, hdr, e))
715 continue;
716
717 ret = update_download_image(fit, e);
718 if (ret) {
719 TFTPUD_E("\"%s\" download fail, ret=%d\n",
720 e->file_name, ret);
721 goto out;
722 }
723
724 ret = update_flash_image(e);
725 if (ret) {
726 TFTPUD_E("\"%s\" flash fail, ret=%d\n",
727 e->file_name, ret);
728 goto out;
729 }
730
731 total_success++;
732 }
733
734 if (total_success == 0) {
735 if (hdr->spec_partition) {
736 TFTPUD_E("No %s partition was found\n", hdr->spec_partition);
737 ret = CMD_RET_FAILURE;
738 }
739 goto out;
740 }
741
742 /* If this is full upgrade, update local info */
743 if (!hdr->spec_partition)
744 update_local_info(fit, hdr);
745 out:
746 update_cleanup(fit, hdr);
747 if (!ret) {
748 total_ms = get_timer(start_ms);
749 TFTPUD_I("tftpupdate is OK (total time: %lds, upgrade: %d/%d), "
750 "system reboot is recommend\n",
751 total_ms / 1000, total_success, total_traverse);
752 }
753
754 return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
755 }
756
757 U_BOOT_CMD(
758 tftp_full_update, 2, 1, do_tftp_full_update,
759 "Update a set of images organized with FIT via network using TFTP protocol",
760 "[[server-dir:][partition]"
761 );
762
763