xref: /openbmc/u-boot/tools/kwboot.c (revision d978780b)
1 /*
2  * Boot a Marvell Kirkwood SoC, with Xmodem over UART0.
3  *
4  * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com>
5  *
6  * References: marvell.com, "88F6180, 88F6190, 88F6192, and 88F6281
7  *   Integrated Controller: Functional Specifications" December 2,
8  *   2008. Chapter 24.2 "BootROM Firmware".
9  */
10 
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <stdarg.h>
15 #include <libgen.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <unistd.h>
19 #include <stdint.h>
20 #include <termios.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 
24 #include "kwbimage.h"
25 
26 #ifdef __GNUC__
27 #define PACKED __attribute((packed))
28 #else
29 #define PACKED
30 #endif
31 
32 /*
33  * Marvell BootROM UART Sensing
34  */
35 
36 static unsigned char kwboot_msg_boot[] = {
37 	0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
38 };
39 
40 #define KWBOOT_MSG_REQ_DELAY	10 /* ms */
41 #define KWBOOT_MSG_RSP_TIMEO	50 /* ms */
42 
43 /*
44  * Xmodem Transfers
45  */
46 
47 #define SOH	1	/* sender start of block header */
48 #define EOT	4	/* sender end of block transfer */
49 #define ACK	6	/* target block ack */
50 #define NAK	21	/* target block negative ack */
51 #define CAN	24	/* target/sender transfer cancellation */
52 
53 struct kwboot_block {
54 	uint8_t soh;
55 	uint8_t pnum;
56 	uint8_t _pnum;
57 	uint8_t data[128];
58 	uint8_t csum;
59 } PACKED;
60 
61 #define KWBOOT_BLK_RSP_TIMEO 1000 /* ms */
62 
63 static int kwboot_verbose;
64 
65 static void
66 kwboot_printv(const char *fmt, ...)
67 {
68 	va_list ap;
69 
70 	if (kwboot_verbose) {
71 		va_start(ap, fmt);
72 		vprintf(fmt, ap);
73 		va_end(ap);
74 		fflush(stdout);
75 	}
76 }
77 
78 static void
79 __spinner(void)
80 {
81 	const char seq[] = { '-', '\\', '|', '/' };
82 	const int div = 8;
83 	static int state, bs;
84 
85 	if (state % div == 0) {
86 		fputc(bs, stdout);
87 		fputc(seq[state / div % sizeof(seq)], stdout);
88 		fflush(stdout);
89 	}
90 
91 	bs = '\b';
92 	state++;
93 }
94 
95 static void
96 kwboot_spinner(void)
97 {
98 	if (kwboot_verbose)
99 		__spinner();
100 }
101 
102 static void
103 __progress(int pct, char c)
104 {
105 	const int width = 70;
106 	static const char *nl = "";
107 	static int pos;
108 
109 	if (pos % width == 0)
110 		printf("%s%3d %% [", nl, pct);
111 
112 	fputc(c, stdout);
113 
114 	nl = "]\n";
115 	pos++;
116 
117 	if (pct == 100) {
118 		while (pos++ < width)
119 			fputc(' ', stdout);
120 		fputs(nl, stdout);
121 	}
122 
123 	fflush(stdout);
124 
125 }
126 
127 static void
128 kwboot_progress(int _pct, char c)
129 {
130 	static int pct;
131 
132 	if (_pct != -1)
133 		pct = _pct;
134 
135 	if (kwboot_verbose)
136 		__progress(pct, c);
137 }
138 
139 static int
140 kwboot_tty_recv(int fd, void *buf, size_t len, int timeo)
141 {
142 	int rc, nfds;
143 	fd_set rfds;
144 	struct timeval tv;
145 	ssize_t n;
146 
147 	rc = -1;
148 
149 	FD_ZERO(&rfds);
150 	FD_SET(fd, &rfds);
151 
152 	tv.tv_sec = 0;
153 	tv.tv_usec = timeo * 1000;
154 	if (tv.tv_usec > 1000000) {
155 		tv.tv_sec += tv.tv_usec / 1000000;
156 		tv.tv_usec %= 1000000;
157 	}
158 
159 	do {
160 		nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
161 		if (nfds < 0)
162 			goto out;
163 		if (!nfds) {
164 			errno = ETIMEDOUT;
165 			goto out;
166 		}
167 
168 		n = read(fd, buf, len);
169 		if (n < 0)
170 			goto out;
171 
172 		buf = (char *)buf + n;
173 		len -= n;
174 	} while (len > 0);
175 
176 	rc = 0;
177 out:
178 	return rc;
179 }
180 
181 static int
182 kwboot_tty_send(int fd, const void *buf, size_t len)
183 {
184 	int rc;
185 	ssize_t n;
186 
187 	rc = -1;
188 
189 	do {
190 		n = write(fd, buf, len);
191 		if (n < 0)
192 			goto out;
193 
194 		buf = (char *)buf + n;
195 		len -= n;
196 	} while (len > 0);
197 
198 	rc = tcdrain(fd);
199 out:
200 	return rc;
201 }
202 
203 static int
204 kwboot_tty_send_char(int fd, unsigned char c)
205 {
206 	return kwboot_tty_send(fd, &c, 1);
207 }
208 
209 static speed_t
210 kwboot_tty_speed(int baudrate)
211 {
212 	switch (baudrate) {
213 	case 115200:
214 		return B115200;
215 	case 57600:
216 		return B57600;
217 	case 38400:
218 		return B38400;
219 	case 19200:
220 		return B19200;
221 	case 9600:
222 		return B9600;
223 	}
224 
225 	return -1;
226 }
227 
228 static int
229 kwboot_open_tty(const char *path, speed_t speed)
230 {
231 	int rc, fd;
232 	struct termios tio;
233 
234 	rc = -1;
235 
236 	fd = open(path, O_RDWR|O_NOCTTY|O_NDELAY);
237 	if (fd < 0)
238 		goto out;
239 
240 	memset(&tio, 0, sizeof(tio));
241 
242 	tio.c_iflag = 0;
243 	tio.c_cflag = CREAD|CLOCAL|CS8;
244 
245 	tio.c_cc[VMIN] = 1;
246 	tio.c_cc[VTIME] = 10;
247 
248 	cfsetospeed(&tio, speed);
249 	cfsetispeed(&tio, speed);
250 
251 	rc = tcsetattr(fd, TCSANOW, &tio);
252 	if (rc)
253 		goto out;
254 
255 	rc = fd;
256 out:
257 	if (rc < 0) {
258 		if (fd >= 0)
259 			close(fd);
260 	}
261 
262 	return rc;
263 }
264 
265 static int
266 kwboot_bootmsg(int tty, void *msg)
267 {
268 	int rc;
269 	char c;
270 
271 	kwboot_printv("Sending boot message. Please reboot the target...");
272 
273 	do {
274 		rc = tcflush(tty, TCIOFLUSH);
275 		if (rc)
276 			break;
277 
278 		rc = kwboot_tty_send(tty, msg, 8);
279 		if (rc) {
280 			usleep(KWBOOT_MSG_REQ_DELAY * 1000);
281 			continue;
282 		}
283 
284 		rc = kwboot_tty_recv(tty, &c, 1, KWBOOT_MSG_RSP_TIMEO);
285 
286 		kwboot_spinner();
287 
288 	} while (rc || c != NAK);
289 
290 	kwboot_printv("\n");
291 
292 	return rc;
293 }
294 
295 static int
296 kwboot_xm_makeblock(struct kwboot_block *block, const void *data,
297 		    size_t size, int pnum)
298 {
299 	const size_t blksz = sizeof(block->data);
300 	size_t n;
301 	int i;
302 
303 	block->pnum = pnum;
304 	block->_pnum = ~block->pnum;
305 
306 	n = size < blksz ? size : blksz;
307 	memcpy(&block->data[0], data, n);
308 	memset(&block->data[n], 0, blksz - n);
309 
310 	block->csum = 0;
311 	for (i = 0; i < n; i++)
312 		block->csum += block->data[i];
313 
314 	return n;
315 }
316 
317 static int
318 kwboot_xm_sendblock(int fd, struct kwboot_block *block)
319 {
320 	int rc, retries;
321 	char c;
322 
323 	retries = 16;
324 	do {
325 		rc = kwboot_tty_send(fd, block, sizeof(*block));
326 		if (rc)
327 			break;
328 
329 		rc = kwboot_tty_recv(fd, &c, 1, KWBOOT_BLK_RSP_TIMEO);
330 		if (rc)
331 			break;
332 
333 		if (c != ACK)
334 			kwboot_progress(-1, '+');
335 
336 	} while (c == NAK && retries-- > 0);
337 
338 	rc = -1;
339 
340 	switch (c) {
341 	case ACK:
342 		rc = 0;
343 		break;
344 	case NAK:
345 		errno = EBADMSG;
346 		break;
347 	case CAN:
348 		errno = ECANCELED;
349 		break;
350 	default:
351 		errno = EPROTO;
352 		break;
353 	}
354 
355 	return rc;
356 }
357 
358 static int
359 kwboot_xmodem(int tty, const void *_data, size_t size)
360 {
361 	const uint8_t *data = _data;
362 	int rc, pnum, N, err;
363 
364 	pnum = 1;
365 	N = 0;
366 
367 	kwboot_printv("Sending boot image...\n");
368 
369 	do {
370 		struct kwboot_block block;
371 		int n;
372 
373 		n = kwboot_xm_makeblock(&block,
374 					data + N, size - N,
375 					pnum++);
376 		if (n < 0)
377 			goto can;
378 
379 		if (!n)
380 			break;
381 
382 		rc = kwboot_xm_sendblock(tty, &block);
383 		if (rc)
384 			goto out;
385 
386 		N += n;
387 		kwboot_progress(N * 100 / size, '.');
388 	} while (1);
389 
390 	rc = kwboot_tty_send_char(tty, EOT);
391 
392 out:
393 	return rc;
394 
395 can:
396 	err = errno;
397 	kwboot_tty_send_char(tty, CAN);
398 	errno = err;
399 	goto out;
400 }
401 
402 static int
403 kwboot_term_pipe(int in, int out, char *quit, int *s)
404 {
405 	ssize_t nin, nout;
406 	char _buf[128], *buf = _buf;
407 
408 	nin = read(in, buf, sizeof(buf));
409 	if (nin < 0)
410 		return -1;
411 
412 	if (quit) {
413 		int i;
414 
415 		for (i = 0; i < nin; i++) {
416 			if (*buf == quit[*s]) {
417 				(*s)++;
418 				if (!quit[*s])
419 					return 0;
420 				buf++;
421 				nin--;
422 			} else
423 				while (*s > 0) {
424 					nout = write(out, quit, *s);
425 					if (nout <= 0)
426 						return -1;
427 					(*s) -= nout;
428 				}
429 		}
430 	}
431 
432 	while (nin > 0) {
433 		nout = write(out, buf, nin);
434 		if (nout <= 0)
435 			return -1;
436 		nin -= nout;
437 	}
438 
439 	return 0;
440 }
441 
442 static int
443 kwboot_terminal(int tty)
444 {
445 	int rc, in, s;
446 	char *quit = "\34c";
447 	struct termios otio, tio;
448 
449 	rc = -1;
450 
451 	in = STDIN_FILENO;
452 	if (isatty(in)) {
453 		rc = tcgetattr(in, &otio);
454 		if (!rc) {
455 			tio = otio;
456 			cfmakeraw(&tio);
457 			rc = tcsetattr(in, TCSANOW, &tio);
458 		}
459 		if (rc) {
460 			perror("tcsetattr");
461 			goto out;
462 		}
463 
464 		kwboot_printv("[Type Ctrl-%c + %c to quit]\r\n",
465 			      quit[0]|0100, quit[1]);
466 	} else
467 		in = -1;
468 
469 	rc = 0;
470 	s = 0;
471 
472 	do {
473 		fd_set rfds;
474 		int nfds = 0;
475 
476 		FD_SET(tty, &rfds);
477 		nfds = nfds < tty ? tty : nfds;
478 
479 		if (in >= 0) {
480 			FD_SET(in, &rfds);
481 			nfds = nfds < in ? in : nfds;
482 		}
483 
484 		nfds = select(nfds + 1, &rfds, NULL, NULL, NULL);
485 		if (nfds < 0)
486 			break;
487 
488 		if (FD_ISSET(tty, &rfds)) {
489 			rc = kwboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL);
490 			if (rc)
491 				break;
492 		}
493 
494 		if (FD_ISSET(in, &rfds)) {
495 			rc = kwboot_term_pipe(in, tty, quit, &s);
496 			if (rc)
497 				break;
498 		}
499 	} while (quit[s] != 0);
500 
501 	tcsetattr(in, TCSANOW, &otio);
502 out:
503 	return rc;
504 }
505 
506 static void *
507 kwboot_mmap_image(const char *path, size_t *size, int prot)
508 {
509 	int rc, fd, flags;
510 	struct stat st;
511 	void *img;
512 
513 	rc = -1;
514 	fd = -1;
515 	img = NULL;
516 
517 	fd = open(path, O_RDONLY);
518 	if (fd < 0)
519 		goto out;
520 
521 	rc = fstat(fd, &st);
522 	if (rc)
523 		goto out;
524 
525 	flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED;
526 
527 	img = mmap(NULL, st.st_size, prot, flags, fd, 0);
528 	if (img == MAP_FAILED) {
529 		img = NULL;
530 		goto out;
531 	}
532 
533 	rc = 0;
534 	*size = st.st_size;
535 out:
536 	if (rc && img) {
537 		munmap(img, st.st_size);
538 		img = NULL;
539 	}
540 	if (fd >= 0)
541 		close(fd);
542 
543 	return img;
544 }
545 
546 static uint8_t
547 kwboot_img_csum8(void *_data, size_t size)
548 {
549 	uint8_t *data = _data, csum;
550 
551 	for (csum = 0; size-- > 0; data++)
552 		csum += *data;
553 
554 	return csum;
555 }
556 
557 static int
558 kwboot_img_patch_hdr(void *img, size_t size)
559 {
560 	int rc;
561 	bhr_t *hdr;
562 	uint8_t csum;
563 	const size_t hdrsz = sizeof(*hdr);
564 
565 	rc = -1;
566 	hdr = img;
567 
568 	if (size < hdrsz) {
569 		errno = EINVAL;
570 		goto out;
571 	}
572 
573 	csum = kwboot_img_csum8(hdr, hdrsz) - hdr->checkSum;
574 	if (csum != hdr->checkSum) {
575 		errno = EINVAL;
576 		goto out;
577 	}
578 
579 	if (hdr->blockid == IBR_HDR_UART_ID) {
580 		rc = 0;
581 		goto out;
582 	}
583 
584 	hdr->blockid = IBR_HDR_UART_ID;
585 
586 	hdr->nandeccmode = IBR_HDR_ECC_DISABLED;
587 	hdr->nandpagesize = 0;
588 
589 	hdr->srcaddr = hdr->ext
590 		? sizeof(struct kwb_header)
591 		: sizeof(*hdr);
592 
593 	hdr->checkSum = kwboot_img_csum8(hdr, hdrsz) - csum;
594 
595 	rc = 0;
596 out:
597 	return rc;
598 }
599 
600 static void
601 kwboot_usage(FILE *stream, char *progname)
602 {
603 	fprintf(stream,
604 		"Usage: %s -b <image> [ -p ] [ -t ] "
605 		"[-B <baud> ] <TTY>\n", progname);
606 	fprintf(stream, "\n");
607 	fprintf(stream, "  -b <image>: boot <image>\n");
608 	fprintf(stream, "  -p: patch <image> to type 0x69 (uart boot)\n");
609 	fprintf(stream, "\n");
610 	fprintf(stream, "  -t: mini terminal\n");
611 	fprintf(stream, "\n");
612 	fprintf(stream, "  -B <baud>: set baud rate\n");
613 	fprintf(stream, "\n");
614 }
615 
616 int
617 main(int argc, char **argv)
618 {
619 	const char *ttypath, *imgpath;
620 	int rv, rc, tty, term, prot, patch;
621 	void *bootmsg;
622 	void *img;
623 	size_t size;
624 	speed_t speed;
625 
626 	rv = 1;
627 	tty = -1;
628 	bootmsg = NULL;
629 	imgpath = NULL;
630 	img = NULL;
631 	term = 0;
632 	patch = 0;
633 	size = 0;
634 	speed = B115200;
635 
636 	kwboot_verbose = isatty(STDOUT_FILENO);
637 
638 	do {
639 		int c = getopt(argc, argv, "hb:ptB:");
640 		if (c < 0)
641 			break;
642 
643 		switch (c) {
644 		case 'b':
645 			bootmsg = kwboot_msg_boot;
646 			imgpath = optarg;
647 			break;
648 
649 		case 'p':
650 			patch = 1;
651 			break;
652 
653 		case 't':
654 			term = 1;
655 			break;
656 
657 		case 'B':
658 			speed = kwboot_tty_speed(atoi(optarg));
659 			if (speed == -1)
660 				goto usage;
661 			break;
662 
663 		case 'h':
664 			rv = 0;
665 		default:
666 			goto usage;
667 		}
668 	} while (1);
669 
670 	if (!bootmsg && !term)
671 		goto usage;
672 
673 	if (patch && !imgpath)
674 		goto usage;
675 
676 	if (argc - optind < 1)
677 		goto usage;
678 
679 	ttypath = argv[optind++];
680 
681 	tty = kwboot_open_tty(ttypath, speed);
682 	if (tty < 0) {
683 		perror(ttypath);
684 		goto out;
685 	}
686 
687 	if (imgpath) {
688 		prot = PROT_READ | (patch ? PROT_WRITE : 0);
689 
690 		img = kwboot_mmap_image(imgpath, &size, prot);
691 		if (!img) {
692 			perror(imgpath);
693 			goto out;
694 		}
695 	}
696 
697 	if (patch) {
698 		rc = kwboot_img_patch_hdr(img, size);
699 		if (rc) {
700 			fprintf(stderr, "%s: Invalid image.\n", imgpath);
701 			goto out;
702 		}
703 	}
704 
705 	if (bootmsg) {
706 		rc = kwboot_bootmsg(tty, bootmsg);
707 		if (rc) {
708 			perror("bootmsg");
709 			goto out;
710 		}
711 	}
712 
713 	if (img) {
714 		rc = kwboot_xmodem(tty, img, size);
715 		if (rc) {
716 			perror("xmodem");
717 			goto out;
718 		}
719 	}
720 
721 	if (term) {
722 		rc = kwboot_terminal(tty);
723 		if (rc && !(errno == EINTR)) {
724 			perror("terminal");
725 			goto out;
726 		}
727 	}
728 
729 	rv = 0;
730 out:
731 	if (tty >= 0)
732 		close(tty);
733 
734 	if (img)
735 		munmap(img, size);
736 
737 	return rv;
738 
739 usage:
740 	kwboot_usage(rv ? stderr : stdout, basename(argv[0]));
741 	goto out;
742 }
743