blob: c5f4492b5f7ab3d4a3b1a609740316cebcb7dae5 [file] [log] [blame]
Luka Perkovdff289642012-05-27 11:44:51 +00001/*
Stefan Roesebb5c4282014-10-22 12:13:21 +02002 * Boot a Marvell SoC, with Xmodem over UART0.
3 * supports Kirkwood, Dove, Armada 370, Armada XP
Luka Perkovdff289642012-05-27 11:44:51 +00004 *
5 * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com>
6 *
7 * References: marvell.com, "88F6180, 88F6190, 88F6192, and 88F6281
8 * Integrated Controller: Functional Specifications" December 2,
9 * 2008. Chapter 24.2 "BootROM Firmware".
10 */
11
12#include <stdlib.h>
13#include <stdio.h>
14#include <string.h>
15#include <stdarg.h>
16#include <libgen.h>
17#include <fcntl.h>
18#include <errno.h>
19#include <unistd.h>
20#include <stdint.h>
21#include <termios.h>
22#include <sys/mman.h>
23#include <sys/stat.h>
24
25#include "kwbimage.h"
26
27#ifdef __GNUC__
28#define PACKED __attribute((packed))
29#else
30#define PACKED
31#endif
32
33/*
34 * Marvell BootROM UART Sensing
35 */
36
37static unsigned char kwboot_msg_boot[] = {
38 0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
39};
40
Stefan Roesebb5c4282014-10-22 12:13:21 +020041static unsigned char kwboot_msg_debug[] = {
42 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
43};
44
45/* Defines known to work on Kirkwood */
Luka Perkovdff289642012-05-27 11:44:51 +000046#define KWBOOT_MSG_REQ_DELAY 10 /* ms */
47#define KWBOOT_MSG_RSP_TIMEO 50 /* ms */
48
Stefan Roesebb5c4282014-10-22 12:13:21 +020049/* Defines known to work on Armada XP */
50#define KWBOOT_MSG_REQ_DELAY_AXP 1000 /* ms */
51#define KWBOOT_MSG_RSP_TIMEO_AXP 1000 /* ms */
52
Luka Perkovdff289642012-05-27 11:44:51 +000053/*
54 * Xmodem Transfers
55 */
56
57#define SOH 1 /* sender start of block header */
58#define EOT 4 /* sender end of block transfer */
59#define ACK 6 /* target block ack */
60#define NAK 21 /* target block negative ack */
61#define CAN 24 /* target/sender transfer cancellation */
62
63struct kwboot_block {
64 uint8_t soh;
65 uint8_t pnum;
66 uint8_t _pnum;
67 uint8_t data[128];
68 uint8_t csum;
69} PACKED;
70
71#define KWBOOT_BLK_RSP_TIMEO 1000 /* ms */
72
73static int kwboot_verbose;
74
Stefan Roesebb5c4282014-10-22 12:13:21 +020075static int msg_req_delay = KWBOOT_MSG_REQ_DELAY;
76static int msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO;
77
Luka Perkovdff289642012-05-27 11:44:51 +000078static void
79kwboot_printv(const char *fmt, ...)
80{
81 va_list ap;
82
83 if (kwboot_verbose) {
84 va_start(ap, fmt);
85 vprintf(fmt, ap);
86 va_end(ap);
87 fflush(stdout);
88 }
89}
90
91static void
92__spinner(void)
93{
94 const char seq[] = { '-', '\\', '|', '/' };
95 const int div = 8;
96 static int state, bs;
97
98 if (state % div == 0) {
99 fputc(bs, stdout);
100 fputc(seq[state / div % sizeof(seq)], stdout);
101 fflush(stdout);
102 }
103
104 bs = '\b';
105 state++;
106}
107
108static void
109kwboot_spinner(void)
110{
111 if (kwboot_verbose)
112 __spinner();
113}
114
115static void
116__progress(int pct, char c)
117{
118 const int width = 70;
119 static const char *nl = "";
120 static int pos;
121
122 if (pos % width == 0)
123 printf("%s%3d %% [", nl, pct);
124
125 fputc(c, stdout);
126
127 nl = "]\n";
128 pos++;
129
130 if (pct == 100) {
131 while (pos++ < width)
132 fputc(' ', stdout);
133 fputs(nl, stdout);
134 }
135
136 fflush(stdout);
137
138}
139
140static void
141kwboot_progress(int _pct, char c)
142{
143 static int pct;
144
145 if (_pct != -1)
146 pct = _pct;
147
148 if (kwboot_verbose)
149 __progress(pct, c);
150}
151
152static int
153kwboot_tty_recv(int fd, void *buf, size_t len, int timeo)
154{
155 int rc, nfds;
156 fd_set rfds;
157 struct timeval tv;
158 ssize_t n;
159
160 rc = -1;
161
162 FD_ZERO(&rfds);
163 FD_SET(fd, &rfds);
164
165 tv.tv_sec = 0;
166 tv.tv_usec = timeo * 1000;
167 if (tv.tv_usec > 1000000) {
168 tv.tv_sec += tv.tv_usec / 1000000;
169 tv.tv_usec %= 1000000;
170 }
171
172 do {
173 nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
174 if (nfds < 0)
175 goto out;
176 if (!nfds) {
177 errno = ETIMEDOUT;
178 goto out;
179 }
180
181 n = read(fd, buf, len);
182 if (n < 0)
183 goto out;
184
185 buf = (char *)buf + n;
186 len -= n;
187 } while (len > 0);
188
189 rc = 0;
190out:
191 return rc;
192}
193
194static int
195kwboot_tty_send(int fd, const void *buf, size_t len)
196{
197 int rc;
198 ssize_t n;
199
Stefan Roesebb5c4282014-10-22 12:13:21 +0200200 if (!buf)
201 return 0;
202
Luka Perkovdff289642012-05-27 11:44:51 +0000203 rc = -1;
204
205 do {
206 n = write(fd, buf, len);
207 if (n < 0)
208 goto out;
209
210 buf = (char *)buf + n;
211 len -= n;
212 } while (len > 0);
213
214 rc = tcdrain(fd);
215out:
216 return rc;
217}
218
219static int
220kwboot_tty_send_char(int fd, unsigned char c)
221{
222 return kwboot_tty_send(fd, &c, 1);
223}
224
225static speed_t
226kwboot_tty_speed(int baudrate)
227{
228 switch (baudrate) {
229 case 115200:
230 return B115200;
231 case 57600:
232 return B57600;
233 case 38400:
234 return B38400;
235 case 19200:
236 return B19200;
237 case 9600:
238 return B9600;
239 }
240
241 return -1;
242}
243
244static int
245kwboot_open_tty(const char *path, speed_t speed)
246{
247 int rc, fd;
248 struct termios tio;
249
250 rc = -1;
251
252 fd = open(path, O_RDWR|O_NOCTTY|O_NDELAY);
253 if (fd < 0)
254 goto out;
255
256 memset(&tio, 0, sizeof(tio));
257
258 tio.c_iflag = 0;
259 tio.c_cflag = CREAD|CLOCAL|CS8;
260
261 tio.c_cc[VMIN] = 1;
262 tio.c_cc[VTIME] = 10;
263
264 cfsetospeed(&tio, speed);
265 cfsetispeed(&tio, speed);
266
267 rc = tcsetattr(fd, TCSANOW, &tio);
268 if (rc)
269 goto out;
270
271 rc = fd;
272out:
273 if (rc < 0) {
274 if (fd >= 0)
275 close(fd);
276 }
277
278 return rc;
279}
280
281static int
282kwboot_bootmsg(int tty, void *msg)
283{
284 int rc;
285 char c;
286
Stefan Roesebb5c4282014-10-22 12:13:21 +0200287 if (msg == NULL)
288 kwboot_printv("Please reboot the target into UART boot mode...");
289 else
290 kwboot_printv("Sending boot message. Please reboot the target...");
Luka Perkovdff289642012-05-27 11:44:51 +0000291
292 do {
293 rc = tcflush(tty, TCIOFLUSH);
294 if (rc)
295 break;
296
297 rc = kwboot_tty_send(tty, msg, 8);
298 if (rc) {
Stefan Roesebb5c4282014-10-22 12:13:21 +0200299 usleep(msg_req_delay * 1000);
Luka Perkovdff289642012-05-27 11:44:51 +0000300 continue;
301 }
302
Stefan Roesebb5c4282014-10-22 12:13:21 +0200303 rc = kwboot_tty_recv(tty, &c, 1, msg_rsp_timeo);
Luka Perkovdff289642012-05-27 11:44:51 +0000304
305 kwboot_spinner();
306
307 } while (rc || c != NAK);
308
309 kwboot_printv("\n");
310
311 return rc;
312}
313
314static int
Stefan Roesebb5c4282014-10-22 12:13:21 +0200315kwboot_debugmsg(int tty, void *msg)
316{
317 int rc;
318
319 kwboot_printv("Sending debug message. Please reboot the target...");
320
321 do {
322 char buf[16];
323
324 rc = tcflush(tty, TCIOFLUSH);
325 if (rc)
326 break;
327
328 rc = kwboot_tty_send(tty, msg, 8);
329 if (rc) {
330 usleep(msg_req_delay * 1000);
331 continue;
332 }
333
334 rc = kwboot_tty_recv(tty, buf, 16, msg_rsp_timeo);
335
336 kwboot_spinner();
337
338 } while (rc);
339
340 kwboot_printv("\n");
341
342 return rc;
343}
344
345static int
Luka Perkovdff289642012-05-27 11:44:51 +0000346kwboot_xm_makeblock(struct kwboot_block *block, const void *data,
347 size_t size, int pnum)
348{
349 const size_t blksz = sizeof(block->data);
350 size_t n;
351 int i;
352
Stefan Roesebb5c4282014-10-22 12:13:21 +0200353 block->soh = SOH;
Luka Perkovdff289642012-05-27 11:44:51 +0000354 block->pnum = pnum;
355 block->_pnum = ~block->pnum;
356
357 n = size < blksz ? size : blksz;
358 memcpy(&block->data[0], data, n);
359 memset(&block->data[n], 0, blksz - n);
360
361 block->csum = 0;
362 for (i = 0; i < n; i++)
363 block->csum += block->data[i];
364
365 return n;
366}
367
368static int
369kwboot_xm_sendblock(int fd, struct kwboot_block *block)
370{
371 int rc, retries;
372 char c;
373
374 retries = 16;
375 do {
376 rc = kwboot_tty_send(fd, block, sizeof(*block));
377 if (rc)
378 break;
379
Stefan Roesebb5c4282014-10-22 12:13:21 +0200380 do {
381 rc = kwboot_tty_recv(fd, &c, 1, KWBOOT_BLK_RSP_TIMEO);
382 if (rc)
383 break;
384
385 if (c != ACK && c != NAK && c != CAN)
386 printf("%c", c);
387
388 } while (c != ACK && c != NAK && c != CAN);
Luka Perkovdff289642012-05-27 11:44:51 +0000389
390 if (c != ACK)
391 kwboot_progress(-1, '+');
392
393 } while (c == NAK && retries-- > 0);
394
395 rc = -1;
396
397 switch (c) {
398 case ACK:
399 rc = 0;
400 break;
401 case NAK:
402 errno = EBADMSG;
403 break;
404 case CAN:
405 errno = ECANCELED;
406 break;
407 default:
408 errno = EPROTO;
409 break;
410 }
411
412 return rc;
413}
414
415static int
416kwboot_xmodem(int tty, const void *_data, size_t size)
417{
418 const uint8_t *data = _data;
419 int rc, pnum, N, err;
420
421 pnum = 1;
422 N = 0;
423
424 kwboot_printv("Sending boot image...\n");
425
426 do {
427 struct kwboot_block block;
428 int n;
429
430 n = kwboot_xm_makeblock(&block,
431 data + N, size - N,
432 pnum++);
433 if (n < 0)
434 goto can;
435
436 if (!n)
437 break;
438
439 rc = kwboot_xm_sendblock(tty, &block);
440 if (rc)
441 goto out;
442
443 N += n;
444 kwboot_progress(N * 100 / size, '.');
445 } while (1);
446
447 rc = kwboot_tty_send_char(tty, EOT);
448
449out:
450 return rc;
451
452can:
453 err = errno;
454 kwboot_tty_send_char(tty, CAN);
455 errno = err;
456 goto out;
457}
458
459static int
460kwboot_term_pipe(int in, int out, char *quit, int *s)
461{
462 ssize_t nin, nout;
463 char _buf[128], *buf = _buf;
464
465 nin = read(in, buf, sizeof(buf));
466 if (nin < 0)
467 return -1;
468
469 if (quit) {
470 int i;
471
472 for (i = 0; i < nin; i++) {
473 if (*buf == quit[*s]) {
474 (*s)++;
475 if (!quit[*s])
476 return 0;
477 buf++;
478 nin--;
479 } else
480 while (*s > 0) {
481 nout = write(out, quit, *s);
482 if (nout <= 0)
483 return -1;
484 (*s) -= nout;
485 }
486 }
487 }
488
489 while (nin > 0) {
490 nout = write(out, buf, nin);
491 if (nout <= 0)
492 return -1;
493 nin -= nout;
494 }
495
496 return 0;
497}
498
499static int
500kwboot_terminal(int tty)
501{
502 int rc, in, s;
503 char *quit = "\34c";
504 struct termios otio, tio;
505
506 rc = -1;
507
508 in = STDIN_FILENO;
509 if (isatty(in)) {
510 rc = tcgetattr(in, &otio);
511 if (!rc) {
512 tio = otio;
513 cfmakeraw(&tio);
514 rc = tcsetattr(in, TCSANOW, &tio);
515 }
516 if (rc) {
517 perror("tcsetattr");
518 goto out;
519 }
520
521 kwboot_printv("[Type Ctrl-%c + %c to quit]\r\n",
522 quit[0]|0100, quit[1]);
523 } else
524 in = -1;
525
526 rc = 0;
527 s = 0;
528
529 do {
530 fd_set rfds;
531 int nfds = 0;
532
533 FD_SET(tty, &rfds);
534 nfds = nfds < tty ? tty : nfds;
535
536 if (in >= 0) {
537 FD_SET(in, &rfds);
538 nfds = nfds < in ? in : nfds;
539 }
540
541 nfds = select(nfds + 1, &rfds, NULL, NULL, NULL);
542 if (nfds < 0)
543 break;
544
545 if (FD_ISSET(tty, &rfds)) {
546 rc = kwboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL);
547 if (rc)
548 break;
549 }
550
551 if (FD_ISSET(in, &rfds)) {
552 rc = kwboot_term_pipe(in, tty, quit, &s);
553 if (rc)
554 break;
555 }
556 } while (quit[s] != 0);
557
558 tcsetattr(in, TCSANOW, &otio);
559out:
560 return rc;
561}
562
563static void *
564kwboot_mmap_image(const char *path, size_t *size, int prot)
565{
566 int rc, fd, flags;
567 struct stat st;
568 void *img;
569
570 rc = -1;
Luka Perkovdff289642012-05-27 11:44:51 +0000571 img = NULL;
572
573 fd = open(path, O_RDONLY);
574 if (fd < 0)
575 goto out;
576
577 rc = fstat(fd, &st);
578 if (rc)
579 goto out;
580
581 flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED;
582
583 img = mmap(NULL, st.st_size, prot, flags, fd, 0);
584 if (img == MAP_FAILED) {
585 img = NULL;
586 goto out;
587 }
588
589 rc = 0;
590 *size = st.st_size;
591out:
592 if (rc && img) {
593 munmap(img, st.st_size);
594 img = NULL;
595 }
596 if (fd >= 0)
597 close(fd);
598
599 return img;
600}
601
602static uint8_t
603kwboot_img_csum8(void *_data, size_t size)
604{
605 uint8_t *data = _data, csum;
606
607 for (csum = 0; size-- > 0; data++)
608 csum += *data;
609
610 return csum;
611}
612
613static int
614kwboot_img_patch_hdr(void *img, size_t size)
615{
616 int rc;
Stefan Roesec74165d2015-09-29 09:19:59 +0200617 struct main_hdr_v1 *hdr;
Luka Perkovdff289642012-05-27 11:44:51 +0000618 uint8_t csum;
Stefan Roesec74165d2015-09-29 09:19:59 +0200619 size_t hdrsz = sizeof(*hdr);
620 int image_ver;
Luka Perkovdff289642012-05-27 11:44:51 +0000621
622 rc = -1;
623 hdr = img;
624
625 if (size < hdrsz) {
626 errno = EINVAL;
627 goto out;
628 }
629
Stefan Roesec74165d2015-09-29 09:19:59 +0200630 image_ver = image_version(img);
631 if (image_ver < 0) {
632 fprintf(stderr, "Invalid image header version\n");
Luka Perkovdff289642012-05-27 11:44:51 +0000633 errno = EINVAL;
634 goto out;
635 }
636
Stefan Roesec74165d2015-09-29 09:19:59 +0200637 if (image_ver == 0)
638 hdrsz = sizeof(*hdr);
639 else
640 hdrsz = KWBHEADER_V1_SIZE(hdr);
641
642 csum = kwboot_img_csum8(hdr, hdrsz) - hdr->checksum;
643 if (csum != hdr->checksum) {
644 errno = EINVAL;
645 goto out;
646 }
647
Luka Perkovdff289642012-05-27 11:44:51 +0000648 if (hdr->blockid == IBR_HDR_UART_ID) {
649 rc = 0;
650 goto out;
651 }
652
653 hdr->blockid = IBR_HDR_UART_ID;
654
Stefan Roesec74165d2015-09-29 09:19:59 +0200655 if (image_ver == 0) {
656 struct main_hdr_v0 *hdr_v0 = img;
Luka Perkovdff289642012-05-27 11:44:51 +0000657
Stefan Roesec74165d2015-09-29 09:19:59 +0200658 hdr_v0->nandeccmode = IBR_HDR_ECC_DISABLED;
659 hdr_v0->nandpagesize = 0;
660
661 hdr_v0->srcaddr = hdr_v0->ext
662 ? sizeof(struct kwb_header)
663 : sizeof(*hdr_v0);
664 }
Luka Perkovdff289642012-05-27 11:44:51 +0000665
Stefan Roesec74165d2015-09-29 09:19:59 +0200666 hdr->checksum = kwboot_img_csum8(hdr, hdrsz) - csum;
Luka Perkovdff289642012-05-27 11:44:51 +0000667
668 rc = 0;
669out:
670 return rc;
671}
672
673static void
674kwboot_usage(FILE *stream, char *progname)
675{
676 fprintf(stream,
Stefan Roesef7f509d2015-05-29 13:25:04 +0200677 "Usage: %s [-d | -a | -q <req-delay> | -s <resp-timeo> | -b <image> | -D <image> ] [ -t ] [-B <baud> ] <TTY>\n",
Stefan Roesebb5c4282014-10-22 12:13:21 +0200678 progname);
Luka Perkovdff289642012-05-27 11:44:51 +0000679 fprintf(stream, "\n");
Stefan Roesebb5c4282014-10-22 12:13:21 +0200680 fprintf(stream,
681 " -b <image>: boot <image> with preamble (Kirkwood, Armada 370/XP)\n");
Luka Perkovdff289642012-05-27 11:44:51 +0000682 fprintf(stream, " -p: patch <image> to type 0x69 (uart boot)\n");
Stefan Roesebb5c4282014-10-22 12:13:21 +0200683 fprintf(stream,
684 " -D <image>: boot <image> without preamble (Dove)\n");
685 fprintf(stream, " -d: enter debug mode\n");
686 fprintf(stream, " -a: use timings for Armada XP\n");
Stefan Roesef7f509d2015-05-29 13:25:04 +0200687 fprintf(stream, " -q <req-delay>: use specific request-delay\n");
688 fprintf(stream, " -s <resp-timeo>: use specific response-timeout\n");
Luka Perkovdff289642012-05-27 11:44:51 +0000689 fprintf(stream, "\n");
690 fprintf(stream, " -t: mini terminal\n");
691 fprintf(stream, "\n");
692 fprintf(stream, " -B <baud>: set baud rate\n");
693 fprintf(stream, "\n");
694}
695
696int
697main(int argc, char **argv)
698{
699 const char *ttypath, *imgpath;
700 int rv, rc, tty, term, prot, patch;
701 void *bootmsg;
Stefan Roesebb5c4282014-10-22 12:13:21 +0200702 void *debugmsg;
Luka Perkovdff289642012-05-27 11:44:51 +0000703 void *img;
704 size_t size;
705 speed_t speed;
706
707 rv = 1;
708 tty = -1;
709 bootmsg = NULL;
Stefan Roesebb5c4282014-10-22 12:13:21 +0200710 debugmsg = NULL;
Luka Perkovdff289642012-05-27 11:44:51 +0000711 imgpath = NULL;
712 img = NULL;
713 term = 0;
714 patch = 0;
715 size = 0;
716 speed = B115200;
717
718 kwboot_verbose = isatty(STDOUT_FILENO);
719
720 do {
Stefan Roesef7f509d2015-05-29 13:25:04 +0200721 int c = getopt(argc, argv, "hb:ptaB:dD:q:s:");
Luka Perkovdff289642012-05-27 11:44:51 +0000722 if (c < 0)
723 break;
724
725 switch (c) {
726 case 'b':
727 bootmsg = kwboot_msg_boot;
728 imgpath = optarg;
729 break;
730
Stefan Roesebb5c4282014-10-22 12:13:21 +0200731 case 'D':
732 bootmsg = NULL;
733 imgpath = optarg;
734 break;
735
736 case 'd':
737 debugmsg = kwboot_msg_debug;
738 break;
739
Luka Perkovdff289642012-05-27 11:44:51 +0000740 case 'p':
741 patch = 1;
742 break;
743
744 case 't':
745 term = 1;
746 break;
747
Stefan Roesebb5c4282014-10-22 12:13:21 +0200748 case 'a':
749 msg_req_delay = KWBOOT_MSG_REQ_DELAY_AXP;
750 msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO_AXP;
751 break;
752
Stefan Roesef7f509d2015-05-29 13:25:04 +0200753 case 'q':
754 msg_req_delay = atoi(optarg);
755 break;
756
757 case 's':
758 msg_rsp_timeo = atoi(optarg);
759 break;
760
Luka Perkovdff289642012-05-27 11:44:51 +0000761 case 'B':
762 speed = kwboot_tty_speed(atoi(optarg));
763 if (speed == -1)
764 goto usage;
765 break;
766
767 case 'h':
768 rv = 0;
769 default:
770 goto usage;
771 }
772 } while (1);
773
Stefan Roesebb5c4282014-10-22 12:13:21 +0200774 if (!bootmsg && !term && !debugmsg)
Luka Perkovdff289642012-05-27 11:44:51 +0000775 goto usage;
776
777 if (patch && !imgpath)
778 goto usage;
779
780 if (argc - optind < 1)
781 goto usage;
782
783 ttypath = argv[optind++];
784
785 tty = kwboot_open_tty(ttypath, speed);
786 if (tty < 0) {
787 perror(ttypath);
788 goto out;
789 }
790
791 if (imgpath) {
792 prot = PROT_READ | (patch ? PROT_WRITE : 0);
793
794 img = kwboot_mmap_image(imgpath, &size, prot);
795 if (!img) {
796 perror(imgpath);
797 goto out;
798 }
799 }
800
801 if (patch) {
802 rc = kwboot_img_patch_hdr(img, size);
803 if (rc) {
804 fprintf(stderr, "%s: Invalid image.\n", imgpath);
805 goto out;
806 }
807 }
808
Stefan Roesebb5c4282014-10-22 12:13:21 +0200809 if (debugmsg) {
810 rc = kwboot_debugmsg(tty, debugmsg);
811 if (rc) {
812 perror("debugmsg");
813 goto out;
814 }
815 } else {
Luka Perkovdff289642012-05-27 11:44:51 +0000816 rc = kwboot_bootmsg(tty, bootmsg);
817 if (rc) {
818 perror("bootmsg");
819 goto out;
820 }
821 }
822
823 if (img) {
824 rc = kwboot_xmodem(tty, img, size);
825 if (rc) {
826 perror("xmodem");
827 goto out;
828 }
829 }
830
831 if (term) {
832 rc = kwboot_terminal(tty);
833 if (rc && !(errno == EINTR)) {
834 perror("terminal");
835 goto out;
836 }
837 }
838
839 rv = 0;
840out:
841 if (tty >= 0)
842 close(tty);
843
844 if (img)
845 munmap(img, size);
846
847 return rv;
848
849usage:
850 kwboot_usage(rv ? stderr : stdout, basename(argv[0]));
851 goto out;
852}