xref: /rk3399_rockchip-uboot/cmd/tftp_update.c (revision 0aca89f213e8a80b8eff5385303341b2304c527d)
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 
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 
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 
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 
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 
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 
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 
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 static int hdr_checksum_verify(void *fit, struct update_header *hdr)
250 {
251 	u8 *hash, csum[SHA256_HASH_SIZE];
252 	int ret, i;
253 
254 	hash = (u8 *)fit + fdt_totalsize(fit);
255 	sha256_csum((const uchar *)fit, fdt_totalsize(fit), csum);
256 	ret = memcmp(hash, csum, SHA256_HASH_SIZE) ? -EINVAL : 0;
257 	if (ret) {
258 		printf(" update.hash: ");
259 		for (i = 0; i < SHA256_HASH_SIZE; i++)
260 			printf("%02x", hash[i]);
261 		printf("\n");
262 
263 		printf(" calculate hash: ");
264 		for (i = 0; i < SHA256_HASH_SIZE; i++)
265 			printf("%02x", csum[i]);
266 		printf("\n");
267 	}
268 
269 	return ret;
270 }
271 #endif
272 
273 static void print_hdr_local(struct update_header *hdr,
274 			    struct local_information *local)
275 {
276 	printf("# Server:\n");
277 	printf("         version: %d\n", hdr->version);
278 	printf("    rollback_idx: %d\n", hdr->rollback_idx);
279 	printf("    force_update: %d\n", hdr->force_update);
280 	printf("              MB: %d\n", hdr->mb);
281 	printf("        lba_step: 0x%08x\n", hdr->lba_step);
282 	printf("      shared_buf: 0x%08lx - 0x%08lx\n",
283 	       (ulong)hdr->shared_buf, (ulong)hdr->shared_buf + hdr->mb * SZ_1M);
284 	printf("  spec_partition: %s\n\n", hdr->spec_partition);
285 
286 	printf("# Local:\n");
287 	printf("         version: %d\n", local->version);
288 	printf("    rollback_idx: %d\n", local->rollback_idx);
289 	printf("    current_slot: %s\n", local->current_slot);
290 	printf("\n");
291 }
292 
293 static int hdr_param_verify(void *fit, struct update_header *hdr,
294 			    struct local_information *local, int conf)
295 {
296 	u32 size;
297 	int ret;
298 
299 	/* remote */
300 	hdr->version = fdtdec_get_uint(fit, 0, "version", 0);
301 	hdr->rollback_idx = fdtdec_get_uint(fit, conf, "rollback-index", 0);
302 	hdr->force_update = fdtdec_get_uint(fit, conf, "force_update", 0);
303 	hdr->mb = fdtdec_get_uint(fit, conf, "image-size-MB", 0);
304 	size = hdr->mb * SZ_1M;
305 	hdr->lba_step = size / 512;
306 	/* TODO: use sysmem alloc/free */
307 	hdr->shared_buf = malloc(size);
308 	if (!hdr->shared_buf)
309 		return -ENOMEM;
310 
311 	/* local */
312 	ret = tftpfw_version_get();
313 	if (ret < 0) {
314 		TFTPUD_E("Failed to get local firmware version, ret=%d\n", ret);
315 		return local->version;
316 	}
317 	local->version = ret;
318 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
319 	u32 remote_rollback_idx;
320 
321 	ret = fit_rollback_index_verify(fit, FIT_ROLLBACK_INDEX,
322 					&remote_rollback_idx, &local->rollback_idx);
323 	if (ret) {
324 		TFTPUD_E("Failed to get local rollback-index, ret=%d\n", ret);
325 		return ret;
326 	}
327 #else
328 	local->rollback_idx = -1;
329 #endif
330 #ifdef CONFIG_ANDROID_AB
331 	ret = rk_avb_get_current_slot(local->current_slot);
332 	if (ret) {
333 		TFTPUD_E("Failed to get local current slot, ret=%d\n", ret);
334 		return ret;
335 	}
336 #else
337 	strcpy(local->current_slot, "-");
338 #endif
339 
340 	print_hdr_local(hdr, local);
341 
342 	/* verify */
343 	if (hdr->force_update) {
344 		TFTPUD_I("Remote requires force upgrade !\n");
345 		return 0;
346 	}
347 	if (hdr->version < local->version) {
348 		TFTPUD_E("Invalid firmware version: %d(remote) < %d(local)\n",
349 			 hdr->version, local->version);
350 		return -EINVAL;
351 	}
352 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
353 	if (remote_rollback_idx < local->rollback_idx) {
354 		TFTPUD_E("Invalid rollback-index: %d(remote) < %d(local)\n",
355 			 remote_rollback_idx, local->rollback_idx);
356 		return -EINVAL;
357 	}
358 #endif
359 
360 	return 0;
361 }
362 
363 static int update_verify_hdr(void *fit, struct update_header *hdr,
364 			     struct local_information *local)
365 {
366 	const char *name;
367 	int noffset;
368 	int conf;
369 	int ret;
370 
371 	noffset = fdt_path_offset(fit, FIT_CONFS_PATH);
372 	name = fdt_getprop(fit, noffset, "default", NULL);
373 	conf = fdt_subnode_offset(fit, noffset, name);
374 	if (conf < 0)
375 		return conf;
376 
377 #ifdef CONFIG_FIT_SIGNATURE
378 	/* Secure: verify signature */
379 	ret = fit_config_verify(fit, conf);
380 	if (ret)
381 		return ret;
382 
383 	TFTPUD_I("hdr signature verified\n");
384 #else
385 	/* Non-secure: verify hash */
386 	ret = hdr_checksum_verify(fit, hdr);
387 	if (ret)
388 		return ret;
389 
390 	TFTPUD_I("hdr checksum verified\n");
391 #endif
392 	/* verify rollback index ..., etc */
393 	ret = hdr_param_verify(fit, hdr, local, conf);
394 	if (ret)
395 		return ret;
396 
397 	TFTPUD_I("hdr param verified\n");
398 
399 	return 0;
400 }
401 
402 static int update_local_info(void *fit, struct update_header *hdr)
403 {
404 	int ret;
405 
406 	TFTPUD_I("Update local information... ");
407 
408 	ret = tftpfw_version_set(hdr->version);
409 	if (ret) {
410 		TFTPUD_E("Update local param FAIL, ret=%d\n", ret);
411 		return ret;
412 	}
413 	printf("fw_version=%d ", hdr->version);
414 
415 #ifdef CONFIG_FIT_ROLLBACK_PROTECT
416 	ret = fit_write_trusty_rollback_index(hdr->rollback_idx);
417 	if (ret)
418 		return ret;
419 	printf("rollback_idx=%d ", hdr->rollback_idx);
420 #endif
421 	printf("\n");
422 
423 	return 0;
424 }
425 
426 static int update_ignore_image(void *fit, struct update_header *hdr,
427 			       struct image_element *e)
428 {
429 #ifdef CONFIG_ANDROID_AB
430 	char *slot_suffix;
431 
432 	/* Android A/B skip current slot */
433 	slot_suffix = (char *)e->part_name + strlen(e->part_name) - 2;
434 	if (!strcmp(hdr->current_slot, slot_suffix))
435 		return 1;
436 #endif
437 	/* try to find expected target partition */
438 	if (hdr->spec_partition && strcmp(e->part_name, hdr->spec_partition))
439 		return 1;
440 
441 	return 0;
442 }
443 
444 static int download_image(void *fit, struct image_element *e)
445 {
446 	ulong fileaddr;
447 	ulong filesize;
448 	char *msg = "";
449 	int ret;
450 
451 	/* download */
452 	printf("[TFTPUD-0]: download \"%s\" at 0x%lx\n",
453 	       e->file_name, (ulong)e->buf);
454 
455 	ret = tftp_download(e->buf, e->file_name);
456 	if (ret)
457 		return ret;
458 
459 	fileaddr = env_get_ulong("fileaddr", 16, 0);
460 	filesize = env_get_ulong("filesize", 16, 0);
461 	if (!fileaddr || !filesize) {
462 		TFTPUD_E("No fileaddr and filesize\n");
463 		return -ENOENT;
464 	}
465 
466 	if (filesize != e->size) {
467 		TFTPUD_E("Expected filesize 0x%08lx != 0x%08x\n", filesize, e->size);
468 		return -EINVAL;
469 	}
470 
471 	/* verify */
472 	printf("[TFTPUD-1]: verify ");
473 	ret = fit_image_check_hash(fit, e->hash_noffset, e->buf, e->size, &msg);
474 	printf("[%s]\n", ret ? "-" : "+");
475 
476 	return ret;
477 }
478 
479 static int update_flash_image(struct image_element *e)
480 {
481 	struct blk_desc *dev_desc;
482 	int ret;
483 
484 	dev_desc = rockchip_get_bootdev();
485 	if (!dev_desc) {
486 		TFTPUD_E("No boot device\n");
487 		return -ENODEV;
488 	}
489 
490 	printf("[TFTPUD-2]: Flash to \"%s\" partition at LBA offset 0x%08x, "
491 	       "with 0x%08x sectors ... ",
492 	       e->part_name, e->lba_offset, e->lba_cnt);
493 
494 	if (dev_desc->if_type == IF_TYPE_MTD) {
495 		dev_desc->op_flag |= BLK_MTD_CONT_WRITE;
496 		ret = blk_dwrite(dev_desc, e->lba_start + e->lba_offset,
497 				 e->lba_cnt, (void *)e->buf);
498 		dev_desc->op_flag &= ~(BLK_MTD_CONT_WRITE);
499 	} else {
500 		ret = blk_dwrite(dev_desc, e->lba_start + e->lba_offset,
501 				 e->lba_cnt, (void *)e->buf);
502 	}
503 
504 	if (ret != e->lba_cnt)
505 		printf("Failed(%d)\n\n\n", ret);
506 	else
507 		printf("OK\n\n\n");
508 
509 	return 0;
510 }
511 
512 static int update_download_image(void *fit, struct image_element *e)
513 {
514 	int i, ret;
515 
516 	for (i = 0; i < e->remain_tries; i++) {
517 		ret = download_image(fit, e);
518 		if (!ret)
519 			return 0;
520 
521 		TFTPUD_E("retry-%d download\n", i);
522 		continue;
523 	}
524 
525 	return -ENODATA;
526 }
527 
528 static int update_write_gpt(void *fit, struct update_header *hdr)
529 {
530 	struct image_element *e;
531 	char *gpt_parts, *p;
532 	const char *name;
533 	int images;
534 	int noffset;
535 	int ret = 0;
536 
537 	images = fdt_path_offset(fit, FIT_IMAGES_PATH);
538 	if (images < 0)
539 		return images;
540 
541 	noffset = fdt_first_subnode(fit, images);
542 	if (noffset < 0)
543 		return noffset;
544 
545 	/* gpt must be the 1st node */
546 	name = fit_get_name(fit, noffset, NULL);
547 	if (!is_gpt(name))
548 		return 0;
549 
550 	e = malloc(sizeof(*e));
551 	if (!e)
552 		return -ENOMEM;
553 
554 	e->remain_tries = MAX_REMAIN_TRIES;
555 	e->buf = hdr->shared_buf;
556 	e->size = fdtdec_get_uint(fit, noffset, "data-size", -ENODATA);
557 	if (e->size == -ENODATA) {
558 		ret = -EINVAL;
559 		goto out;
560 	}
561 
562 	strcpy(e->file_name, name);
563 	e->hash_noffset = fdt_subnode_offset(fit, noffset, "hash");
564 	if (e->hash_noffset < 0)
565 		return e->hash_noffset;
566 
567 	printf("\n# %s:\n", e->file_name);
568 	printf("             buf: 0x%08lx\n", (ulong)e->buf);
569 	printf("            size: 0x%08x\n", e->size);
570 	printf("    remain_tries: %d\n", e->remain_tries);
571 	printf("    hash_noffset: 0x%08x\n\n", e->hash_noffset);
572 
573 	/* download */
574 	ret = update_download_image(fit, e);
575 	if (ret) {
576 		TFTPUD_E("\"%s\" download fail, ret=%d\n",
577 			 e->file_name, ret);
578 		goto out;
579 	}
580 
581 	/* terminate gpt string */
582 	gpt_parts = (char *)e->buf;
583 	p = gpt_parts + e->size - 1;
584 	*p = '\0';
585 
586 	/* write */
587 	printf("[TFTPUD-2]: Write gpt ...\n");
588 	printf("    %s\n\n", gpt_parts);
589 	env_set("gpt_parts", gpt_parts);
590 	ret = run_command("gpt write ${devtype} ${devnum} ${gpt_parts}", 0);
591 	if (ret) {
592 		printf("Failed to write gpt\n");
593 		ret = -EIO;
594 		goto out;
595 	}
596 	ret = run_command("gpt verify ${devtype} ${devnum} ${gpt_parts}", 0);
597 	if (ret) {
598 		printf("Failed to verify gpt\n");
599 		ret = -EIO;
600 		goto out;
601 	}
602 	printf("\n");
603 out:
604 	free(e);
605 
606 	return ret;
607 }
608 
609 static int do_tftp_full_update(cmd_tbl_t *cmdtp, int flag,
610 			       int argc, char * const argv[])
611 {
612 	struct local_information *local = &local_info;
613 	struct update_header *hdr = &update_hdr;
614 	struct image_element *e;
615 	struct list_head *node;
616 	const char *dir_part_str;
617 	const char *part_str;
618 	const char *dir_str;
619 	char *dup_str = NULL;
620 	u32 total_success = 0;
621 	u32 total_traverse = 0;
622 	ulong start_ms;
623 	ulong total_ms;
624 	void *fit;
625 	int ret;
626 
627 	start_ms = get_timer(0);
628 	memset(hdr, 0, sizeof(*hdr));
629 	memset(local, 0, sizeof(*local));
630 
631 	/* only handle a single partititon ? */
632 	if (argc > 1) {
633 		dir_part_str = argv[1];
634 		part_str = strchr(dir_part_str, ':');
635 		if (part_str) {
636 			/*
637 			 * eg: tftpupdate image:recovery
638 			 *     tftpupdate image:*
639 			 *     tftpupdate image:
640 			 */
641 			dup_str = strdup(dir_part_str);
642 			dup_str[part_str - dir_part_str] = 0;
643 			dir_str = dup_str;
644 			part_str++;
645 			if (*part_str == '*')
646 				part_str = NULL;
647 		} else {
648 			/* eg: tftpupdate recovery */
649 			dir_str = NULL;
650 			part_str = argv[1];
651 		}
652 	} else {
653 		dir_str = NULL;
654 		part_str = NULL;
655 	}
656 
657 	server_dir = dir_str;
658 	hdr->spec_partition = part_str;
659 	INIT_LIST_HEAD(&hdr->images);
660 
661 	fit = update_download_hdr(hdr);
662 	if (!fit) {
663 		TFTPUD_E("download hdr fail\n");
664 		ret = -EINVAL;
665 		goto out;
666 	}
667 
668 	ret = update_verify_hdr(fit, hdr, local);
669 	if (ret) {
670 		TFTPUD_E("verify hdr fail, ret=%d\n", ret);
671 		goto out;
672 	}
673 
674 	/* flash gpt table early than any other partition */
675 	ret = update_write_gpt(fit, hdr);
676 	if (ret) {
677 		TFTPUD_E("write gpt fail, ret=%d\n", ret);
678 		goto out;
679 	}
680 
681 	ret = update_populate_image(fit, hdr);
682 	if (ret) {
683 		TFTPUD_E("populate image fail, ret=%d\n", ret);
684 		goto out;
685 	}
686 
687 	list_for_each(node, &hdr->images) {
688 		e = list_entry(node, struct image_element, node);
689 		total_traverse++;
690 
691 		/* ignore ? */
692 		if (update_ignore_image(fit, hdr, e))
693 			continue;
694 
695 		ret = update_download_image(fit, e);
696 		if (ret) {
697 			TFTPUD_E("\"%s\" download fail, ret=%d\n",
698 				 e->file_name, ret);
699 			goto out;
700 		}
701 
702 		ret = update_flash_image(e);
703 		if (ret) {
704 			TFTPUD_E("\"%s\" flash fail, ret=%d\n",
705 				 e->file_name, ret);
706 			goto out;
707 		}
708 
709 		total_success++;
710 	}
711 
712 	if (total_success == 0) {
713 		if (hdr->spec_partition) {
714 			TFTPUD_E("No %s partition was found\n", hdr->spec_partition);
715 			ret = CMD_RET_FAILURE;
716 		}
717 		goto out;
718 	}
719 
720 	/* If this is full upgrade, update local info */
721 	if (!hdr->spec_partition)
722 		update_local_info(fit, hdr);
723 out:
724 	update_cleanup(fit, hdr);
725 	if (!ret) {
726 		total_ms = get_timer(start_ms);
727 		TFTPUD_I("tftpupdate is OK (total time: %lds, upgrade: %d/%d), "
728 			 "system reboot is recommend\n",
729 			 total_ms / 1000, total_success, total_traverse);
730 	}
731 
732 	return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
733 }
734 
735 U_BOOT_CMD(
736 	tftp_full_update, 2, 1, do_tftp_full_update,
737 	"Update a set of images organized with FIT via network using TFTP protocol",
738 	"[[server-dir:][partition]"
739 );
740 
741