| /* | |
| * Boot a Marvell SoC, with Xmodem over UART0. | |
| * supports Kirkwood, Dove, Avanta, Armada 370, Armada XP, Armada 375, | |
| * Armada 38x and Armada 39x. | |
| * | |
| * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com> | |
| * (c) 2021 Pali Rohár <pali@kernel.org> | |
| * (c) 2021 Marek Behún <kabel@kernel.org> | |
| * | |
| * References: | |
| * - "88F6180, 88F6190, 88F6192, and 88F6281: Integrated Controller: Functional | |
| * Specifications" December 2, 2008. Chapter 24.2 "BootROM Firmware". | |
| * https://web.archive.org/web/20130730091033/https://www.marvell.com/embedded-processors/kirkwood/assets/FS_88F6180_9x_6281_OpenSource.pdf | |
| * - "88AP510: High-Performance SoC with Integrated CPU, 2D/3D Graphics | |
| * Processor, and High-Definition Video Decoder: Functional Specifications" | |
| * August 3, 2011. Chapter 5 "BootROM Firmware" | |
| * https://web.archive.org/web/20120130172443/https://www.marvell.com/application-processors/armada-500/assets/Armada-510-Functional-Spec.pdf | |
| * - "88F6710, 88F6707, and 88F6W11: ARMADA(R) 370 SoC: Functional Specifications" | |
| * May 26, 2014. Chapter 6 "BootROM Firmware". | |
| * https://web.archive.org/web/20140617183701/https://www.marvell.com/embedded-processors/armada-300/assets/ARMADA370-FunctionalSpec-datasheet.pdf | |
| * - "MV78230, MV78260, and MV78460: ARMADA(R) XP Family of Highly Integrated | |
| * Multi-Core ARMv7 Based SoC Processors: Functional Specifications" | |
| * May 29, 2014. Chapter 6 "BootROM Firmware". | |
| * https://web.archive.org/web/20180829171131/https://www.marvell.com/embedded-processors/armada-xp/assets/ARMADA-XP-Functional-SpecDatasheet.pdf | |
| * - "ARMADA(R) 375 Value-Performance Dual Core CPU System on Chip: Functional | |
| * Specifications" Doc. No. MV-S109377-00, Rev. A. September 18, 2013. | |
| * Chapter 7 "Boot Sequence" | |
| * CONFIDENTIAL, no public documentation available | |
| * - "88F6810, 88F6811, 88F6821, 88F6W21, 88F6820, and 88F6828: ARMADA(R) 38x | |
| * Family High-Performance Single/Dual CPU System on Chip: Functional | |
| * Specifications" Doc. No. MV-S109094-00, Rev. C. August 2, 2015. | |
| * Chapter 7 "Boot Flow" | |
| * CONFIDENTIAL, no public documentation available | |
| * - "88F6920, 88F6925 and 88F6928: ARMADA(R) 39x High-Performance Dual Core CPU | |
| * System on Chip Functional Specifications" Doc. No. MV-S109896-00, Rev. B. | |
| * December 22, 2015. Chapter 7 "Boot Flow" | |
| * CONFIDENTIAL, no public documentation available | |
| */ | |
| #include "kwbimage.h" | |
| #include "mkimage.h" | |
| #include "version.h" | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <stdarg.h> | |
| #include <image.h> | |
| #include <libgen.h> | |
| #include <fcntl.h> | |
| #include <errno.h> | |
| #include <unistd.h> | |
| #include <stdint.h> | |
| #include <time.h> | |
| #include <sys/stat.h> | |
| #include <pthread.h> | |
| #ifdef __linux__ | |
| #include "termios_linux.h" | |
| #else | |
| #include <termios.h> | |
| #endif | |
| /* | |
| * These functions are in <term.h> header file, but this header file conflicts | |
| * with "termios_linux.h" header file. So declare these functions manually. | |
| */ | |
| extern int setupterm(const char *, int, int *); | |
| extern char *tigetstr(const char *); | |
| /* | |
| * Marvell BootROM UART Sensing | |
| */ | |
| static unsigned char kwboot_msg_boot[] = { | |
| 0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 | |
| }; | |
| static unsigned char kwboot_msg_debug[] = { | |
| 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 | |
| }; | |
| /* Defines known to work on Kirkwood */ | |
| #define KWBOOT_MSG_RSP_TIMEO 50 /* ms */ | |
| /* Defines known to work on Armada XP */ | |
| #define KWBOOT_MSG_RSP_TIMEO_AXP 10 /* ms */ | |
| /* | |
| * Xmodem Transfers | |
| */ | |
| #define SOH 1 /* sender start of block header */ | |
| #define EOT 4 /* sender end of block transfer */ | |
| #define ACK 6 /* target block ack */ | |
| #define NAK 21 /* target block negative ack */ | |
| #define KWBOOT_XM_BLKSZ 128 /* xmodem block size */ | |
| struct kwboot_block { | |
| uint8_t soh; | |
| uint8_t pnum; | |
| uint8_t _pnum; | |
| uint8_t data[KWBOOT_XM_BLKSZ]; | |
| uint8_t csum; | |
| } __packed; | |
| #define KWBOOT_BLK_RSP_TIMEO 2000 /* ms */ | |
| #define KWBOOT_HDR_RSP_TIMEO 10000 /* ms */ | |
| /* ARM code to change baudrate */ | |
| static unsigned char kwboot_baud_code[] = { | |
| /* ; #define UART_BASE 0xd0012000 */ | |
| /* ; #define DLL 0x00 */ | |
| /* ; #define DLH 0x04 */ | |
| /* ; #define LCR 0x0c */ | |
| /* ; #define DLAB 0x80 */ | |
| /* ; #define LSR 0x14 */ | |
| /* ; #define TEMT 0x40 */ | |
| /* ; #define DIV_ROUND(a, b) ((a + b/2) / b) */ | |
| /* ; */ | |
| /* ; u32 set_baudrate(u32 old_b, u32 new_b) { */ | |
| /* ; while */ | |
| /* ; (!(readl(UART_BASE + LSR) & TEMT)); */ | |
| /* ; u32 lcr = readl(UART_BASE + LCR); */ | |
| /* ; writel(UART_BASE + LCR, lcr | DLAB); */ | |
| /* ; u8 old_dll = readl(UART_BASE + DLL); */ | |
| /* ; u8 old_dlh = readl(UART_BASE + DLH); */ | |
| /* ; u16 old_dl = old_dll | (old_dlh << 8); */ | |
| /* ; u32 clk = old_b * old_dl; */ | |
| /* ; u16 new_dl = DIV_ROUND(clk, new_b); */ | |
| /* ; u8 new_dll = new_dl & 0xff; */ | |
| /* ; u8 new_dlh = (new_dl >> 8) & 0xff; */ | |
| /* ; writel(UART_BASE + DLL, new_dll); */ | |
| /* ; writel(UART_BASE + DLH, new_dlh); */ | |
| /* ; writel(UART_BASE + LCR, lcr & ~DLAB); */ | |
| /* ; msleep(5); */ | |
| /* ; return 0; */ | |
| /* ; } */ | |
| /* ; r0 = UART_BASE */ | |
| 0x0d, 0x02, 0xa0, 0xe3, /* mov r0, #0xd0000000 */ | |
| 0x12, 0x0a, 0x80, 0xe3, /* orr r0, r0, #0x12000 */ | |
| /* ; Wait until Transmitter FIFO is Empty */ | |
| /* .Lloop_txempty: */ | |
| /* ; r1 = UART_BASE[LSR] & TEMT */ | |
| 0x14, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x14] */ | |
| 0x40, 0x00, 0x11, 0xe3, /* tst r1, #0x40 */ | |
| 0xfc, 0xff, 0xff, 0x0a, /* beq .Lloop_txempty */ | |
| /* ; Set Divisor Latch Access Bit */ | |
| /* ; UART_BASE[LCR] |= DLAB */ | |
| 0x0c, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x0c] */ | |
| 0x80, 0x10, 0x81, 0xe3, /* orr r1, r1, #0x80 */ | |
| 0x0c, 0x10, 0x80, 0xe5, /* str r1, [r0, #0x0c] */ | |
| /* ; Read current Divisor Latch */ | |
| /* ; r1 = UART_BASE[DLH]<<8 | UART_BASE[DLL] */ | |
| 0x00, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x00] */ | |
| 0xff, 0x10, 0x01, 0xe2, /* and r1, r1, #0xff */ | |
| 0x01, 0x20, 0xa0, 0xe1, /* mov r2, r1 */ | |
| 0x04, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x04] */ | |
| 0xff, 0x10, 0x01, 0xe2, /* and r1, r1, #0xff */ | |
| 0x41, 0x14, 0xa0, 0xe1, /* asr r1, r1, #8 */ | |
| 0x02, 0x10, 0x81, 0xe1, /* orr r1, r1, r2 */ | |
| /* ; Read old baudrate value */ | |
| /* ; r2 = old_baudrate */ | |
| 0x74, 0x20, 0x9f, 0xe5, /* ldr r2, old_baudrate */ | |
| /* ; Calculate base clock */ | |
| /* ; r1 = r2 * r1 */ | |
| 0x92, 0x01, 0x01, 0xe0, /* mul r1, r2, r1 */ | |
| /* ; Read new baudrate value */ | |
| /* ; r2 = new_baudrate */ | |
| 0x70, 0x20, 0x9f, 0xe5, /* ldr r2, new_baudrate */ | |
| /* ; Calculate new Divisor Latch */ | |
| /* ; r1 = DIV_ROUND(r1, r2) = */ | |
| /* ; = (r1 + r2/2) / r2 */ | |
| 0xa2, 0x10, 0x81, 0xe0, /* add r1, r1, r2, lsr #1 */ | |
| 0x02, 0x40, 0xa0, 0xe1, /* mov r4, r2 */ | |
| 0xa1, 0x00, 0x54, 0xe1, /* cmp r4, r1, lsr #1 */ | |
| /* .Lloop_div1: */ | |
| 0x84, 0x40, 0xa0, 0x91, /* movls r4, r4, lsl #1 */ | |
| 0xa1, 0x00, 0x54, 0xe1, /* cmp r4, r1, lsr #1 */ | |
| 0xfc, 0xff, 0xff, 0x9a, /* bls .Lloop_div1 */ | |
| 0x00, 0x30, 0xa0, 0xe3, /* mov r3, #0 */ | |
| /* .Lloop_div2: */ | |
| 0x04, 0x00, 0x51, 0xe1, /* cmp r1, r4 */ | |
| 0x04, 0x10, 0x41, 0x20, /* subhs r1, r1, r4 */ | |
| 0x03, 0x30, 0xa3, 0xe0, /* adc r3, r3, r3 */ | |
| 0xa4, 0x40, 0xa0, 0xe1, /* mov r4, r4, lsr #1 */ | |
| 0x02, 0x00, 0x54, 0xe1, /* cmp r4, r2 */ | |
| 0xf9, 0xff, 0xff, 0x2a, /* bhs .Lloop_div2 */ | |
| 0x03, 0x10, 0xa0, 0xe1, /* mov r1, r3 */ | |
| /* ; Set new Divisor Latch Low */ | |
| /* ; UART_BASE[DLL] = r1 & 0xff */ | |
| 0x01, 0x20, 0xa0, 0xe1, /* mov r2, r1 */ | |
| 0xff, 0x20, 0x02, 0xe2, /* and r2, r2, #0xff */ | |
| 0x00, 0x20, 0x80, 0xe5, /* str r2, [r0, #0x00] */ | |
| /* ; Set new Divisor Latch High */ | |
| /* ; UART_BASE[DLH] = r1>>8 & 0xff */ | |
| 0x41, 0x24, 0xa0, 0xe1, /* asr r2, r1, #8 */ | |
| 0xff, 0x20, 0x02, 0xe2, /* and r2, r2, #0xff */ | |
| 0x04, 0x20, 0x80, 0xe5, /* str r2, [r0, #0x04] */ | |
| /* ; Clear Divisor Latch Access Bit */ | |
| /* ; UART_BASE[LCR] &= ~DLAB */ | |
| 0x0c, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x0c] */ | |
| 0x80, 0x10, 0xc1, 0xe3, /* bic r1, r1, #0x80 */ | |
| 0x0c, 0x10, 0x80, 0xe5, /* str r1, [r0, #0x0c] */ | |
| /* ; Loop 0x2dc000 (2998272) cycles */ | |
| /* ; which is about 5ms on 1200 MHz CPU */ | |
| /* ; r1 = 0x2dc000 */ | |
| 0xb7, 0x19, 0xa0, 0xe3, /* mov r1, #0x2dc000 */ | |
| /* .Lloop_sleep: */ | |
| 0x01, 0x10, 0x41, 0xe2, /* sub r1, r1, #1 */ | |
| 0x00, 0x00, 0x51, 0xe3, /* cmp r1, #0 */ | |
| 0xfc, 0xff, 0xff, 0x1a, /* bne .Lloop_sleep */ | |
| /* ; Jump to the end of execution */ | |
| 0x01, 0x00, 0x00, 0xea, /* b end */ | |
| /* ; Placeholder for old baudrate value */ | |
| /* old_baudrate: */ | |
| 0x00, 0x00, 0x00, 0x00, /* .word 0 */ | |
| /* ; Placeholder for new baudrate value */ | |
| /* new_baudrate: */ | |
| 0x00, 0x00, 0x00, 0x00, /* .word 0 */ | |
| /* end: */ | |
| }; | |
| /* ARM code from binary header executed by BootROM before changing baudrate */ | |
| static unsigned char kwboot_baud_code_binhdr_pre[] = { | |
| /* ; #define UART_BASE 0xd0012000 */ | |
| /* ; #define THR 0x00 */ | |
| /* ; #define LSR 0x14 */ | |
| /* ; #define THRE 0x20 */ | |
| /* ; */ | |
| /* ; void send_preamble(void) { */ | |
| /* ; const u8 *str = "$baudratechange"; */ | |
| /* ; u8 c; */ | |
| /* ; do { */ | |
| /* ; while */ | |
| /* ; ((readl(UART_BASE + LSR) & THRE)); */ | |
| /* ; c = *str++; */ | |
| /* ; writel(UART_BASE + THR, c); */ | |
| /* ; } while (c); */ | |
| /* ; } */ | |
| /* ; Preserve registers for BootROM */ | |
| 0xfe, 0x5f, 0x2d, 0xe9, /* push { r1 - r12, lr } */ | |
| /* ; r0 = UART_BASE */ | |
| 0x0d, 0x02, 0xa0, 0xe3, /* mov r0, #0xd0000000 */ | |
| 0x12, 0x0a, 0x80, 0xe3, /* orr r0, r0, #0x12000 */ | |
| /* ; r2 = address of preamble string */ | |
| 0x00, 0x20, 0x8f, 0xe2, /* adr r2, .Lstr_preamble */ | |
| /* ; Skip preamble data section */ | |
| 0x03, 0x00, 0x00, 0xea, /* b .Lloop_preamble */ | |
| /* ; Preamble string */ | |
| /* .Lstr_preamble: */ | |
| 0x24, 0x62, 0x61, 0x75, /* .asciz "$baudratechange" */ | |
| 0x64, 0x72, 0x61, 0x74, | |
| 0x65, 0x63, 0x68, 0x61, | |
| 0x6e, 0x67, 0x65, 0x00, | |
| /* ; Send preamble string over UART */ | |
| /* .Lloop_preamble: */ | |
| /* */ | |
| /* ; Wait until Transmitter Holding is Empty */ | |
| /* .Lloop_thre: */ | |
| /* ; r1 = UART_BASE[LSR] & THRE */ | |
| 0x14, 0x10, 0x90, 0xe5, /* ldr r1, [r0, #0x14] */ | |
| 0x20, 0x00, 0x11, 0xe3, /* tst r1, #0x20 */ | |
| 0xfc, 0xff, 0xff, 0x0a, /* beq .Lloop_thre */ | |
| /* ; Put character into Transmitter FIFO */ | |
| /* ; r1 = *r2++ */ | |
| 0x01, 0x10, 0xd2, 0xe4, /* ldrb r1, [r2], #1 */ | |
| /* ; UART_BASE[THR] = r1 */ | |
| 0x00, 0x10, 0x80, 0xe5, /* str r1, [r0, #0x0] */ | |
| /* ; Loop until end of preamble string */ | |
| 0x00, 0x00, 0x51, 0xe3, /* cmp r1, #0 */ | |
| 0xf8, 0xff, 0xff, 0x1a, /* bne .Lloop_preamble */ | |
| }; | |
| /* ARM code for returning from binary header back to BootROM */ | |
| static unsigned char kwboot_baud_code_binhdr_post[] = { | |
| /* ; Return 0 - no error */ | |
| 0x00, 0x00, 0xa0, 0xe3, /* mov r0, #0 */ | |
| 0xfe, 0x9f, 0xbd, 0xe8, /* pop { r1 - r12, pc } */ | |
| }; | |
| /* ARM code for jumping to the original image exec_addr */ | |
| static unsigned char kwboot_baud_code_data_jump[] = { | |
| 0x04, 0xf0, 0x1f, 0xe5, /* ldr pc, exec_addr */ | |
| /* ; Placeholder for exec_addr */ | |
| /* exec_addr: */ | |
| 0x00, 0x00, 0x00, 0x00, /* .word 0 */ | |
| }; | |
| static const char kwb_baud_magic[16] = "$baudratechange"; | |
| static int kwboot_verbose; | |
| static int msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO; | |
| static int blk_rsp_timeo = KWBOOT_BLK_RSP_TIMEO; | |
| static ssize_t | |
| kwboot_write(int fd, const char *buf, size_t len) | |
| { | |
| ssize_t tot = 0; | |
| while (tot < len) { | |
| ssize_t wr = write(fd, buf + tot, len - tot); | |
| if (wr < 0 && errno == EINTR) | |
| continue; | |
| else if (wr < 0) | |
| return wr; | |
| tot += wr; | |
| } | |
| return tot; | |
| } | |
| static void | |
| kwboot_printv(const char *fmt, ...) | |
| { | |
| va_list ap; | |
| if (kwboot_verbose) { | |
| va_start(ap, fmt); | |
| vprintf(fmt, ap); | |
| va_end(ap); | |
| fflush(stdout); | |
| } | |
| } | |
| static void | |
| __spinner(void) | |
| { | |
| const char seq[] = { '-', '\\', '|', '/' }; | |
| const int div = 8; | |
| static int state, bs; | |
| if (state % div == 0) { | |
| fputc(bs, stdout); | |
| fputc(seq[state / div % sizeof(seq)], stdout); | |
| fflush(stdout); | |
| } | |
| bs = '\b'; | |
| state++; | |
| } | |
| static void | |
| kwboot_spinner(void) | |
| { | |
| if (kwboot_verbose) | |
| __spinner(); | |
| } | |
| static void | |
| __progress(int pct, char c) | |
| { | |
| const int width = 70; | |
| static const char *nl = ""; | |
| static int pos; | |
| if (pos % width == 0) | |
| printf("%s%3d %% [", nl, pct); | |
| fputc(c, stdout); | |
| nl = "]\n"; | |
| pos = (pos + 1) % width; | |
| if (pct == 100) { | |
| while (pos && pos++ < width) | |
| fputc(' ', stdout); | |
| fputs(nl, stdout); | |
| nl = ""; | |
| pos = 0; | |
| } | |
| fflush(stdout); | |
| } | |
| static void | |
| kwboot_progress(int _pct, char c) | |
| { | |
| static int pct; | |
| if (_pct != -1) | |
| pct = _pct; | |
| if (kwboot_verbose) | |
| __progress(pct, c); | |
| if (pct == 100) | |
| pct = 0; | |
| } | |
| static int | |
| kwboot_tty_recv(int fd, void *buf, size_t len, int timeo) | |
| { | |
| int rc, nfds; | |
| fd_set rfds; | |
| struct timeval tv; | |
| ssize_t n; | |
| rc = -1; | |
| FD_ZERO(&rfds); | |
| FD_SET(fd, &rfds); | |
| tv.tv_sec = 0; | |
| tv.tv_usec = timeo * 1000; | |
| if (tv.tv_usec > 1000000) { | |
| tv.tv_sec += tv.tv_usec / 1000000; | |
| tv.tv_usec %= 1000000; | |
| } | |
| do { | |
| nfds = select(fd + 1, &rfds, NULL, NULL, &tv); | |
| if (nfds < 0 && errno == EINTR) | |
| continue; | |
| else if (nfds < 0) | |
| goto out; | |
| else if (!nfds) { | |
| errno = ETIMEDOUT; | |
| goto out; | |
| } | |
| n = read(fd, buf, len); | |
| if (n < 0 && errno == EINTR) | |
| continue; | |
| else if (n <= 0) | |
| goto out; | |
| buf = (char *)buf + n; | |
| len -= n; | |
| } while (len > 0); | |
| rc = 0; | |
| out: | |
| return rc; | |
| } | |
| static int | |
| kwboot_tty_send(int fd, const void *buf, size_t len, int nodrain) | |
| { | |
| if (!buf) | |
| return 0; | |
| if (kwboot_write(fd, buf, len) < 0) | |
| return -1; | |
| if (nodrain) | |
| return 0; | |
| return tcdrain(fd); | |
| } | |
| static int | |
| kwboot_tty_send_char(int fd, unsigned char c) | |
| { | |
| return kwboot_tty_send(fd, &c, 1, 0); | |
| } | |
| static speed_t | |
| kwboot_tty_baudrate_to_speed(int baudrate) | |
| { | |
| switch (baudrate) { | |
| #ifdef B4000000 | |
| case 4000000: | |
| return B4000000; | |
| #endif | |
| #ifdef B3500000 | |
| case 3500000: | |
| return B3500000; | |
| #endif | |
| #ifdef B3000000 | |
| case 3000000: | |
| return B3000000; | |
| #endif | |
| #ifdef B2500000 | |
| case 2500000: | |
| return B2500000; | |
| #endif | |
| #ifdef B2000000 | |
| case 2000000: | |
| return B2000000; | |
| #endif | |
| #ifdef B1500000 | |
| case 1500000: | |
| return B1500000; | |
| #endif | |
| #ifdef B1152000 | |
| case 1152000: | |
| return B1152000; | |
| #endif | |
| #ifdef B1000000 | |
| case 1000000: | |
| return B1000000; | |
| #endif | |
| #ifdef B921600 | |
| case 921600: | |
| return B921600; | |
| #endif | |
| #ifdef B614400 | |
| case 614400: | |
| return B614400; | |
| #endif | |
| #ifdef B576000 | |
| case 576000: | |
| return B576000; | |
| #endif | |
| #ifdef B500000 | |
| case 500000: | |
| return B500000; | |
| #endif | |
| #ifdef B460800 | |
| case 460800: | |
| return B460800; | |
| #endif | |
| #ifdef B307200 | |
| case 307200: | |
| return B307200; | |
| #endif | |
| #ifdef B230400 | |
| case 230400: | |
| return B230400; | |
| #endif | |
| #ifdef B153600 | |
| case 153600: | |
| return B153600; | |
| #endif | |
| #ifdef B115200 | |
| case 115200: | |
| return B115200; | |
| #endif | |
| #ifdef B76800 | |
| case 76800: | |
| return B76800; | |
| #endif | |
| #ifdef B57600 | |
| case 57600: | |
| return B57600; | |
| #endif | |
| #ifdef B38400 | |
| case 38400: | |
| return B38400; | |
| #endif | |
| #ifdef B19200 | |
| case 19200: | |
| return B19200; | |
| #endif | |
| #ifdef B9600 | |
| case 9600: | |
| return B9600; | |
| #endif | |
| #ifdef B4800 | |
| case 4800: | |
| return B4800; | |
| #endif | |
| #ifdef B2400 | |
| case 2400: | |
| return B2400; | |
| #endif | |
| #ifdef B1800 | |
| case 1800: | |
| return B1800; | |
| #endif | |
| #ifdef B1200 | |
| case 1200: | |
| return B1200; | |
| #endif | |
| #ifdef B600 | |
| case 600: | |
| return B600; | |
| #endif | |
| #ifdef B300 | |
| case 300: | |
| return B300; | |
| #endif | |
| #ifdef B200 | |
| case 200: | |
| return B200; | |
| #endif | |
| #ifdef B150 | |
| case 150: | |
| return B150; | |
| #endif | |
| #ifdef B134 | |
| case 134: | |
| return B134; | |
| #endif | |
| #ifdef B110 | |
| case 110: | |
| return B110; | |
| #endif | |
| #ifdef B75 | |
| case 75: | |
| return B75; | |
| #endif | |
| #ifdef B50 | |
| case 50: | |
| return B50; | |
| #endif | |
| default: | |
| #ifdef BOTHER | |
| return BOTHER; | |
| #else | |
| return B0; | |
| #endif | |
| } | |
| } | |
| static int | |
| _is_within_tolerance(int value, int reference, int tolerance) | |
| { | |
| return 100 * value >= reference * (100 - tolerance) && | |
| 100 * value <= reference * (100 + tolerance); | |
| } | |
| static int | |
| kwboot_tty_change_baudrate(int fd, int baudrate) | |
| { | |
| struct termios tio; | |
| speed_t speed; | |
| int rc; | |
| rc = tcgetattr(fd, &tio); | |
| if (rc) | |
| return rc; | |
| speed = kwboot_tty_baudrate_to_speed(baudrate); | |
| if (speed == B0) { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| #ifdef BOTHER | |
| if (speed == BOTHER) | |
| tio.c_ospeed = tio.c_ispeed = baudrate; | |
| #endif | |
| rc = cfsetospeed(&tio, speed); | |
| if (rc) | |
| return rc; | |
| rc = cfsetispeed(&tio, speed); | |
| if (rc) | |
| return rc; | |
| rc = tcsetattr(fd, TCSANOW, &tio); | |
| if (rc) | |
| return rc; | |
| rc = tcgetattr(fd, &tio); | |
| if (rc) | |
| return rc; | |
| if (cfgetospeed(&tio) != speed || cfgetispeed(&tio) != speed) | |
| goto baud_fail; | |
| #ifdef BOTHER | |
| /* | |
| * Check whether set baudrate is within 3% tolerance. | |
| * If BOTHER is defined, Linux always fills out c_ospeed / c_ispeed | |
| * with real values. | |
| */ | |
| if (!_is_within_tolerance(tio.c_ospeed, baudrate, 3)) | |
| goto baud_fail; | |
| if (!_is_within_tolerance(tio.c_ispeed, baudrate, 3)) | |
| goto baud_fail; | |
| #endif | |
| return 0; | |
| baud_fail: | |
| fprintf(stderr, "Could not set baudrate to requested value\n"); | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| static int | |
| kwboot_open_tty(const char *path, int baudrate) | |
| { | |
| int rc, fd, flags; | |
| struct termios tio; | |
| rc = -1; | |
| fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); | |
| if (fd < 0) | |
| goto out; | |
| rc = tcgetattr(fd, &tio); | |
| if (rc) | |
| goto out; | |
| cfmakeraw(&tio); | |
| tio.c_cflag |= CREAD | CLOCAL; | |
| tio.c_cflag &= ~(CSTOPB | HUPCL | CRTSCTS); | |
| tio.c_cc[VMIN] = 1; | |
| tio.c_cc[VTIME] = 0; | |
| rc = tcsetattr(fd, TCSANOW, &tio); | |
| if (rc) | |
| goto out; | |
| flags = fcntl(fd, F_GETFL); | |
| if (flags < 0) | |
| goto out; | |
| rc = fcntl(fd, F_SETFL, flags & ~O_NDELAY); | |
| if (rc) | |
| goto out; | |
| rc = kwboot_tty_change_baudrate(fd, baudrate); | |
| if (rc) | |
| goto out; | |
| rc = fd; | |
| out: | |
| if (rc < 0) { | |
| if (fd >= 0) | |
| close(fd); | |
| } | |
| return rc; | |
| } | |
| static void * | |
| kwboot_msg_write_handler(void *arg) | |
| { | |
| int tty = *(int *)((void **)arg)[0]; | |
| const void *msg = ((void **)arg)[1]; | |
| int rsp_timeo = msg_rsp_timeo; | |
| int i, dummy_oldtype; | |
| /* allow to cancel this thread at any time */ | |
| pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &dummy_oldtype); | |
| while (1) { | |
| /* write 128 samples of message pattern into the output queue without waiting */ | |
| for (i = 0; i < 128; i++) { | |
| if (kwboot_tty_send(tty, msg, 8, 1) < 0) { | |
| perror("\nFailed to send message pattern"); | |
| exit(1); | |
| } | |
| } | |
| /* wait until output queue is transmitted and then make pause */ | |
| if (tcdrain(tty) < 0) { | |
| perror("\nFailed to send message pattern"); | |
| exit(1); | |
| } | |
| /* BootROM requires pause on UART after it detects message pattern */ | |
| usleep(rsp_timeo * 1000); | |
| } | |
| } | |
| static int | |
| kwboot_msg_start_thread(pthread_t *thread, int *tty, void *msg) | |
| { | |
| void *arg[2]; | |
| int rc; | |
| arg[0] = tty; | |
| arg[1] = msg; | |
| rc = pthread_create(thread, NULL, kwboot_msg_write_handler, arg); | |
| if (rc) { | |
| errno = rc; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| kwboot_msg_stop_thread(pthread_t thread) | |
| { | |
| int rc; | |
| rc = pthread_cancel(thread); | |
| if (rc) { | |
| errno = rc; | |
| return -1; | |
| } | |
| rc = pthread_join(thread, NULL); | |
| if (rc) { | |
| errno = rc; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| kwboot_bootmsg(int tty) | |
| { | |
| struct kwboot_block block; | |
| pthread_t write_thread; | |
| int rc, err; | |
| char c; | |
| /* flush input and output queue */ | |
| tcflush(tty, TCIOFLUSH); | |
| rc = kwboot_msg_start_thread(&write_thread, &tty, kwboot_msg_boot); | |
| if (rc) { | |
| perror("Failed to start write thread"); | |
| return rc; | |
| } | |
| kwboot_printv("Sending boot message. Please reboot the target..."); | |
| err = 0; | |
| while (1) { | |
| kwboot_spinner(); | |
| rc = kwboot_tty_recv(tty, &c, 1, msg_rsp_timeo); | |
| if (rc && errno == ETIMEDOUT) { | |
| continue; | |
| } else if (rc) { | |
| err = errno; | |
| break; | |
| } | |
| if (c == NAK) | |
| break; | |
| } | |
| kwboot_printv("\n"); | |
| rc = kwboot_msg_stop_thread(write_thread); | |
| if (rc) { | |
| perror("Failed to stop write thread"); | |
| return rc; | |
| } | |
| if (err) { | |
| errno = err; | |
| perror("Failed to read response for boot message pattern"); | |
| return -1; | |
| } | |
| /* | |
| * At this stage we have sent more boot message patterns and BootROM | |
| * (at least on Armada XP and 385) started interpreting sent bytes as | |
| * part of xmodem packets. If BootROM is expecting SOH byte as start of | |
| * a xmodem packet and it receives byte 0xff, then it throws it away and | |
| * sends a NAK reply to host. If BootROM does not receive any byte for | |
| * 2s when expecting some continuation of the xmodem packet, it throws | |
| * away the partially received xmodem data and sends NAK reply to host. | |
| * | |
| * Therefore for starting xmodem transfer we have two options: Either | |
| * wait 2s or send 132 0xff bytes (which is the size of xmodem packet) | |
| * to ensure that BootROM throws away any partially received data. | |
| */ | |
| /* flush output queue with remaining boot message patterns */ | |
| rc = tcflush(tty, TCOFLUSH); | |
| if (rc) { | |
| perror("Failed to flush output queue"); | |
| return rc; | |
| } | |
| /* send one xmodem packet with 0xff bytes to force BootROM to re-sync */ | |
| memset(&block, 0xff, sizeof(block)); | |
| rc = kwboot_tty_send(tty, &block, sizeof(block), 0); | |
| if (rc) { | |
| perror("Failed to send sync sequence"); | |
| return rc; | |
| } | |
| /* | |
| * Sending 132 bytes via 115200B/8-N-1 takes 11.45 ms, reading 132 bytes | |
| * takes 11.45 ms, so waiting for 30 ms should be enough. | |
| */ | |
| usleep(30 * 1000); | |
| /* flush remaining NAK replies from input queue */ | |
| rc = tcflush(tty, TCIFLUSH); | |
| if (rc) { | |
| perror("Failed to flush input queue"); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| static int | |
| kwboot_debugmsg(int tty) | |
| { | |
| unsigned char buf[8192]; | |
| pthread_t write_thread; | |
| int rc, err, i, pos; | |
| size_t off; | |
| /* flush input and output queue */ | |
| tcflush(tty, TCIOFLUSH); | |
| rc = kwboot_msg_start_thread(&write_thread, &tty, kwboot_msg_debug); | |
| if (rc) { | |
| perror("Failed to start write thread"); | |
| return rc; | |
| } | |
| kwboot_printv("Sending debug message. Please reboot the target..."); | |
| kwboot_spinner(); | |
| err = 0; | |
| off = 0; | |
| while (1) { | |
| /* Read immediately all bytes in queue without waiting */ | |
| rc = read(tty, buf + off, sizeof(buf) - off); | |
| if ((rc < 0 && errno == EINTR) || rc == 0) { | |
| continue; | |
| } else if (rc < 0) { | |
| err = errno; | |
| break; | |
| } | |
| off += rc - 1; | |
| kwboot_spinner(); | |
| /* | |
| * Check if we received at least 4 debug message patterns | |
| * (console echo from BootROM) in cyclic buffer | |
| */ | |
| for (pos = 0; pos < sizeof(kwboot_msg_debug); pos++) | |
| if (buf[off] == kwboot_msg_debug[(pos + off) % sizeof(kwboot_msg_debug)]) | |
| break; | |
| for (i = off; i >= 0; i--) | |
| if (buf[i] != kwboot_msg_debug[(pos + i) % sizeof(kwboot_msg_debug)]) | |
| break; | |
| off -= i; | |
| if (off >= 4 * sizeof(kwboot_msg_debug)) | |
| break; | |
| /* If not move valid suffix from end of the buffer to the beginning of buffer */ | |
| memmove(buf, buf + i + 1, off); | |
| } | |
| kwboot_printv("\n"); | |
| rc = kwboot_msg_stop_thread(write_thread); | |
| if (rc) { | |
| perror("Failed to stop write thread"); | |
| return rc; | |
| } | |
| if (err) { | |
| errno = err; | |
| perror("Failed to read response for debug message pattern"); | |
| return -1; | |
| } | |
| /* flush output queue with remaining debug message patterns */ | |
| rc = tcflush(tty, TCOFLUSH); | |
| if (rc) { | |
| perror("Failed to flush output queue"); | |
| return rc; | |
| } | |
| kwboot_printv("Clearing input buffer...\n"); | |
| /* | |
| * Wait until BootROM transmit all remaining echo characters. | |
| * Experimentally it was measured that for Armada 385 BootROM | |
| * it is required to wait at least 0.415s. So wait 0.5s. | |
| */ | |
| usleep(500 * 1000); | |
| /* | |
| * In off variable is stored number of characters received after the | |
| * successful detection of echo reply. So these characters are console | |
| * echo for other following debug message patterns. BootROM may have in | |
| * its output queue other echo characters which were being transmitting | |
| * before above sleep call. So read remaining number of echo characters | |
| * sent by the BootROM now. | |
| */ | |
| while ((rc = kwboot_tty_recv(tty, &buf[0], 1, 0)) == 0) | |
| off++; | |
| if (errno != ETIMEDOUT) { | |
| perror("Failed to read response"); | |
| return rc; | |
| } | |
| /* | |
| * Clear every echo character set by the BootROM by backspace byte. | |
| * This is required prior writing any command to the BootROM debug | |
| * because BootROM command line buffer has limited size. If length | |
| * of the command is larger than buffer size then it looks like | |
| * that Armada 385 BootROM crashes after sending ENTER. So erase it. | |
| * Experimentally it was measured that for Armada 385 BootROM it is | |
| * required to send at least 3 backspace bytes for one echo character. | |
| * This is unknown why. But lets do it. | |
| */ | |
| off *= 3; | |
| memset(buf, '\x08', sizeof(buf)); | |
| while (off > sizeof(buf)) { | |
| rc = kwboot_tty_send(tty, buf, sizeof(buf), 1); | |
| if (rc) { | |
| perror("Failed to send clear sequence"); | |
| return rc; | |
| } | |
| off -= sizeof(buf); | |
| } | |
| rc = kwboot_tty_send(tty, buf, off, 0); | |
| if (rc) { | |
| perror("Failed to send clear sequence"); | |
| return rc; | |
| } | |
| usleep(msg_rsp_timeo * 1000); | |
| rc = tcflush(tty, TCIFLUSH); | |
| if (rc) { | |
| perror("Failed to flush input queue"); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| static size_t | |
| kwboot_xm_makeblock(struct kwboot_block *block, const void *data, | |
| size_t size, int pnum) | |
| { | |
| size_t i, n; | |
| block->soh = SOH; | |
| block->pnum = pnum; | |
| block->_pnum = ~block->pnum; | |
| n = size < KWBOOT_XM_BLKSZ ? size : KWBOOT_XM_BLKSZ; | |
| memcpy(&block->data[0], data, n); | |
| memset(&block->data[n], 0, KWBOOT_XM_BLKSZ - n); | |
| block->csum = 0; | |
| for (i = 0; i < n; i++) | |
| block->csum += block->data[i]; | |
| return n; | |
| } | |
| static uint64_t | |
| _now(void) | |
| { | |
| struct timespec ts; | |
| if (clock_gettime(CLOCK_MONOTONIC, &ts)) { | |
| static int err_print; | |
| if (!err_print) { | |
| perror("clock_gettime() does not work"); | |
| err_print = 1; | |
| } | |
| /* this will just make the timeout not work */ | |
| return -1ULL; | |
| } | |
| return ts.tv_sec * 1000ULL + (ts.tv_nsec + 500000) / 1000000; | |
| } | |
| static int | |
| _is_xm_reply(char c) | |
| { | |
| return c == ACK || c == NAK; | |
| } | |
| static int | |
| _xm_reply_to_error(int c) | |
| { | |
| int rc = -1; | |
| switch (c) { | |
| case ACK: | |
| rc = 0; | |
| break; | |
| case NAK: | |
| errno = EBADMSG; | |
| break; | |
| default: | |
| errno = EPROTO; | |
| break; | |
| } | |
| return rc; | |
| } | |
| static int | |
| kwboot_baud_magic_handle(int fd, char c, int baudrate) | |
| { | |
| static size_t rcv_len; | |
| if (rcv_len < sizeof(kwb_baud_magic)) { | |
| /* try to recognize whole magic word */ | |
| if (c == kwb_baud_magic[rcv_len]) { | |
| rcv_len++; | |
| } else { | |
| printf("%.*s%c", (int)rcv_len, kwb_baud_magic, c); | |
| fflush(stdout); | |
| rcv_len = 0; | |
| } | |
| } | |
| if (rcv_len == sizeof(kwb_baud_magic)) { | |
| /* magic word received */ | |
| kwboot_printv("\nChanging baudrate to %d Bd\n", baudrate); | |
| return kwboot_tty_change_baudrate(fd, baudrate) ? : 1; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| static int | |
| kwboot_xm_recv_reply(int fd, char *c, int stop_on_non_xm, | |
| int ignore_nak_reply, | |
| int allow_non_xm, int *non_xm_print, | |
| int baudrate, int *baud_changed) | |
| { | |
| int timeout = allow_non_xm ? KWBOOT_HDR_RSP_TIMEO : blk_rsp_timeo; | |
| uint64_t recv_until = _now() + timeout; | |
| int rc; | |
| while (1) { | |
| rc = kwboot_tty_recv(fd, c, 1, timeout); | |
| if (rc) { | |
| if (errno != ETIMEDOUT) | |
| return rc; | |
| else if (allow_non_xm && *non_xm_print) | |
| return -1; | |
| else | |
| *c = NAK; | |
| } | |
| /* If received xmodem reply, end. */ | |
| if (_is_xm_reply(*c)) { | |
| if (*c == NAK && ignore_nak_reply) { | |
| timeout = recv_until - _now(); | |
| if (timeout >= 0) | |
| continue; | |
| } | |
| break; | |
| } | |
| /* | |
| * If receiving/printing non-xmodem text output is allowed and | |
| * such a byte was received, we want to increase receiving time | |
| * and either: | |
| * - print the byte, if it is not part of baudrate change magic | |
| * sequence while baudrate change was requested (-B option) | |
| * - change baudrate | |
| * Otherwise decrease timeout by time elapsed. | |
| */ | |
| if (allow_non_xm) { | |
| recv_until = _now() + timeout; | |
| if (baudrate && !*baud_changed) { | |
| rc = kwboot_baud_magic_handle(fd, *c, baudrate); | |
| if (rc == 1) | |
| *baud_changed = 1; | |
| else if (!rc) | |
| *non_xm_print = 1; | |
| else | |
| return rc; | |
| } else if (!baudrate || !*baud_changed) { | |
| putchar(*c); | |
| fflush(stdout); | |
| *non_xm_print = 1; | |
| } | |
| } else { | |
| if (stop_on_non_xm) | |
| break; | |
| timeout = recv_until - _now(); | |
| if (timeout < 0) { | |
| errno = ETIMEDOUT; | |
| return -1; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static int | |
| kwboot_xm_sendblock(int fd, struct kwboot_block *block, int allow_non_xm, | |
| int *done_print, int baudrate, int allow_retries) | |
| { | |
| int non_xm_print, baud_changed; | |
| int rc, err, retries; | |
| char c; | |
| *done_print = 0; | |
| non_xm_print = 0; | |
| baud_changed = 0; | |
| retries = 0; | |
| do { | |
| rc = kwboot_tty_send(fd, block, sizeof(*block), 1); | |
| if (rc) | |
| goto err; | |
| if (allow_non_xm && !*done_print) { | |
| kwboot_progress(100, '.'); | |
| kwboot_printv("Done\n"); | |
| *done_print = 1; | |
| } | |
| rc = kwboot_xm_recv_reply(fd, &c, retries < 3, | |
| retries > 8, | |
| allow_non_xm, &non_xm_print, | |
| baudrate, &baud_changed); | |
| if (rc) | |
| goto err; | |
| if (!allow_non_xm && c != ACK) { | |
| if (c == NAK && allow_retries && retries + 1 < 16) | |
| kwboot_progress(-1, '+'); | |
| else | |
| kwboot_progress(-1, 'E'); | |
| } | |
| } while (c == NAK && allow_retries && retries++ < 16); | |
| if (non_xm_print) | |
| kwboot_printv("\n"); | |
| if (allow_non_xm && baudrate && !baud_changed) { | |
| fprintf(stderr, "Baudrate was not changed\n"); | |
| errno = EPROTO; | |
| return -1; | |
| } | |
| return _xm_reply_to_error(c); | |
| err: | |
| err = errno; | |
| kwboot_printv("\n"); | |
| errno = err; | |
| return rc; | |
| } | |
| static int | |
| kwboot_xm_finish(int fd) | |
| { | |
| int rc, retries; | |
| char c; | |
| kwboot_printv("Finishing transfer\n"); | |
| retries = 0; | |
| do { | |
| rc = kwboot_tty_send_char(fd, EOT); | |
| if (rc) | |
| return rc; | |
| rc = kwboot_xm_recv_reply(fd, &c, retries < 3, | |
| retries > 8, | |
| 0, NULL, 0, NULL); | |
| if (rc) | |
| return rc; | |
| } while (c == NAK && retries++ < 16); | |
| return _xm_reply_to_error(c); | |
| } | |
| static int | |
| kwboot_xmodem_one(int tty, int *pnum, int header, const uint8_t *data, | |
| size_t size, int baudrate) | |
| { | |
| int done_print = 0; | |
| size_t sent, left; | |
| int rc; | |
| kwboot_printv("Sending boot image %s (%zu bytes)...\n", | |
| header ? "header" : "data", size); | |
| left = size; | |
| sent = 0; | |
| while (sent < size) { | |
| struct kwboot_block block; | |
| int last_block; | |
| size_t blksz; | |
| blksz = kwboot_xm_makeblock(&block, data, left, (*pnum)++); | |
| data += blksz; | |
| last_block = (left <= blksz); | |
| /* | |
| * Handling of repeated xmodem packets is completely broken in | |
| * Armada 385 BootROM - it completely ignores xmodem packet | |
| * numbers, they are only used for checksum verification. | |
| * BootROM can handle a retry of the xmodem packet only during | |
| * the transmission of kwbimage header and only if BootROM | |
| * itself sent NAK response to previous attempt (it does it on | |
| * checksum failure). During the transmission of kwbimage data | |
| * part, BootROM always expects next xmodem packet, even if it | |
| * sent NAK to previous attempt - there is absolutely no way to | |
| * repair incorrectly transmitted xmodem packet during kwbimage | |
| * data part upload. Also, if kwboot receives non-ACK/NAK | |
| * response (meaning that original BootROM response was damaged | |
| * on UART) there is no way to detect if BootROM accepted xmodem | |
| * packet or not and no way to check if kwboot could repeat the | |
| * packet or not. | |
| * | |
| * Stop transfer and return failure if kwboot receives unknown | |
| * reply if non-xmodem reply is not allowed (for all xmodem | |
| * packets except the last header packet) or when non-ACK reply | |
| * is received during data part transfer. | |
| */ | |
| rc = kwboot_xm_sendblock(tty, &block, header && last_block, | |
| &done_print, baudrate, header); | |
| if (rc) | |
| goto out; | |
| sent += blksz; | |
| left -= blksz; | |
| if (!done_print) | |
| kwboot_progress(sent * 100 / size, '.'); | |
| } | |
| if (!done_print) | |
| kwboot_printv("Done\n"); | |
| return 0; | |
| out: | |
| kwboot_printv("\n"); | |
| return rc; | |
| } | |
| static int | |
| kwboot_xmodem(int tty, const void *_img, size_t size, int baudrate) | |
| { | |
| const uint8_t *img = _img; | |
| int rc, pnum; | |
| size_t hdrsz; | |
| hdrsz = kwbheader_size(img); | |
| /* | |
| * If header size is not aligned to xmodem block size (which applies | |
| * for all images in kwbimage v0 format) then we have to ensure that | |
| * the last xmodem block of header contains beginning of the data | |
| * followed by the header. So align header size to xmodem block size. | |
| */ | |
| hdrsz += (KWBOOT_XM_BLKSZ - hdrsz % KWBOOT_XM_BLKSZ) % KWBOOT_XM_BLKSZ; | |
| pnum = 1; | |
| rc = kwboot_xmodem_one(tty, &pnum, 1, img, hdrsz, baudrate); | |
| if (rc) | |
| return rc; | |
| /* | |
| * If we have already sent image data as a part of the last | |
| * xmodem header block then we have nothing more to send. | |
| */ | |
| if (hdrsz < size) { | |
| img += hdrsz; | |
| size -= hdrsz; | |
| rc = kwboot_xmodem_one(tty, &pnum, 0, img, size, 0); | |
| if (rc) | |
| return rc; | |
| } | |
| rc = kwboot_xm_finish(tty); | |
| if (rc) | |
| return rc; | |
| if (baudrate) { | |
| kwboot_printv("\nChanging baudrate back to 115200 Bd\n\n"); | |
| rc = kwboot_tty_change_baudrate(tty, 115200); | |
| if (rc) | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| static int | |
| kwboot_term_pipe(int in, int out, const char *quit, int *s, const char *kbs, int *k) | |
| { | |
| char buf[128]; | |
| ssize_t nin, noff; | |
| nin = read(in, buf, sizeof(buf)); | |
| if (nin <= 0) | |
| return -1; | |
| noff = 0; | |
| if (quit || kbs) { | |
| int i; | |
| for (i = 0; i < nin; i++) { | |
| if ((quit || kbs) && | |
| (!quit || buf[i] != quit[*s]) && | |
| (!kbs || buf[i] != kbs[*k])) { | |
| const char *prefix; | |
| int plen; | |
| if (quit && kbs) { | |
| prefix = (*s >= *k) ? quit : kbs; | |
| plen = (*s >= *k) ? *s : *k; | |
| } else if (quit) { | |
| prefix = quit; | |
| plen = *s; | |
| } else { | |
| prefix = kbs; | |
| plen = *k; | |
| } | |
| if (plen > i && kwboot_write(out, prefix, plen - i) < 0) | |
| return -1; | |
| } | |
| if (quit && buf[i] == quit[*s]) { | |
| (*s)++; | |
| if (!quit[*s]) { | |
| nin = (i > *s) ? (i - *s) : 0; | |
| break; | |
| } | |
| } else if (quit) { | |
| *s = 0; | |
| } | |
| if (kbs && buf[i] == kbs[*k]) { | |
| (*k)++; | |
| if (!kbs[*k]) { | |
| if (i > *k + noff && | |
| kwboot_write(out, buf + noff, i - *k - noff) < 0) | |
| return -1; | |
| /* | |
| * Replace backspace key by '\b' (0x08) | |
| * byte which is the only recognized | |
| * backspace byte by Marvell BootROM. | |
| */ | |
| if (write(out, "\x08", 1) < 0) | |
| return -1; | |
| noff = i + 1; | |
| *k = 0; | |
| } | |
| } else if (kbs) { | |
| *k = 0; | |
| } | |
| } | |
| if (i == nin) { | |
| i = 0; | |
| if (quit && i < *s) | |
| i = *s; | |
| if (kbs && i < *k) | |
| i = *k; | |
| nin -= (nin > i) ? i : nin; | |
| } | |
| } | |
| if (nin > noff && kwboot_write(out, buf + noff, nin - noff) < 0) | |
| return -1; | |
| return 0; | |
| } | |
| static int | |
| kwboot_terminal(int tty) | |
| { | |
| int rc, in, s, k; | |
| const char *kbs = NULL; | |
| const char *quit = "\34c"; | |
| struct termios otio, tio; | |
| rc = -1; | |
| in = STDIN_FILENO; | |
| if (isatty(in)) { | |
| rc = tcgetattr(in, &otio); | |
| if (!rc) { | |
| tio = otio; | |
| cfmakeraw(&tio); | |
| rc = tcsetattr(in, TCSANOW, &tio); | |
| } | |
| if (rc) { | |
| perror("tcsetattr"); | |
| goto out; | |
| } | |
| /* | |
| * Get sequence for backspace key used by the current | |
| * terminal. Every occurrence of this sequence will be | |
| * replaced by '\b' byte which is the only recognized | |
| * backspace byte by Marvell BootROM. | |
| * | |
| * Note that we cannot read this sequence from termios | |
| * c_cc[VERASE] as VERASE is valid only when ICANON is | |
| * set in termios c_lflag, which is not case for us. | |
| * | |
| * Also most terminals do not set termios c_cc[VERASE] | |
| * as c_cc[VERASE] can specify only one-byte sequence | |
| * and instead let applications to read (possible | |
| * multi-byte) sequence for backspace key from "kbs" | |
| * terminfo database based on $TERM env variable. | |
| * | |
| * So read "kbs" from terminfo database via tigetstr() | |
| * call after successful setupterm(). Most terminals | |
| * use byte 0x7F for backspace key, so replacement with | |
| * '\b' is required. | |
| */ | |
| if (setupterm(NULL, STDOUT_FILENO, &rc) == 0) { | |
| kbs = tigetstr("kbs"); | |
| if (kbs == (char *)-1) | |
| kbs = NULL; | |
| } | |
| kwboot_printv("[Type Ctrl-%c + %c to quit]\r\n", | |
| quit[0] | 0100, quit[1]); | |
| } else | |
| in = -1; | |
| rc = 0; | |
| s = 0; | |
| k = 0; | |
| do { | |
| fd_set rfds; | |
| int nfds = 0; | |
| FD_ZERO(&rfds); | |
| FD_SET(tty, &rfds); | |
| nfds = nfds < tty ? tty : nfds; | |
| if (in >= 0) { | |
| FD_SET(in, &rfds); | |
| nfds = nfds < in ? in : nfds; | |
| } | |
| nfds = select(nfds + 1, &rfds, NULL, NULL, NULL); | |
| if (nfds < 0) | |
| break; | |
| if (FD_ISSET(tty, &rfds)) { | |
| rc = kwboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL, NULL, NULL); | |
| if (rc) | |
| break; | |
| } | |
| if (in >= 0 && FD_ISSET(in, &rfds)) { | |
| rc = kwboot_term_pipe(in, tty, quit, &s, kbs, &k); | |
| if (rc) | |
| break; | |
| } | |
| } while (quit[s] != 0); | |
| if (in >= 0) | |
| tcsetattr(in, TCSANOW, &otio); | |
| printf("\n"); | |
| out: | |
| return rc; | |
| } | |
| static void * | |
| kwboot_read_image(const char *path, size_t *size, size_t reserve) | |
| { | |
| int rc, fd; | |
| void *img; | |
| off_t len; | |
| off_t tot; | |
| rc = -1; | |
| img = NULL; | |
| fd = open(path, O_RDONLY); | |
| if (fd < 0) | |
| goto out; | |
| len = lseek(fd, 0, SEEK_END); | |
| if (len == (off_t)-1) | |
| goto out; | |
| if (lseek(fd, 0, SEEK_SET) == (off_t)-1) | |
| goto out; | |
| img = malloc(len + reserve); | |
| if (!img) | |
| goto out; | |
| tot = 0; | |
| while (tot < len) { | |
| ssize_t rd = read(fd, img + tot, len - tot); | |
| if (rd < 0) | |
| goto out; | |
| tot += rd; | |
| if (!rd && tot < len) { | |
| errno = EIO; | |
| goto out; | |
| } | |
| } | |
| rc = 0; | |
| *size = len; | |
| out: | |
| if (rc && img) { | |
| free(img); | |
| img = NULL; | |
| } | |
| if (fd >= 0) | |
| close(fd); | |
| return img; | |
| } | |
| static uint8_t | |
| kwboot_hdr_csum8(const void *hdr) | |
| { | |
| const uint8_t *data = hdr; | |
| uint8_t csum; | |
| size_t size; | |
| size = kwbheader_size_for_csum(hdr); | |
| for (csum = 0; size-- > 0; data++) | |
| csum += *data; | |
| return csum; | |
| } | |
| static uint32_t * | |
| kwboot_img_csum32_ptr(void *img) | |
| { | |
| struct main_hdr_v1 *hdr = img; | |
| uint32_t datasz; | |
| datasz = le32_to_cpu(hdr->blocksize) - sizeof(uint32_t); | |
| return img + le32_to_cpu(hdr->srcaddr) + datasz; | |
| } | |
| static uint32_t | |
| kwboot_img_csum32(const void *img) | |
| { | |
| const struct main_hdr_v1 *hdr = img; | |
| uint32_t datasz, csum = 0; | |
| const uint32_t *data; | |
| datasz = le32_to_cpu(hdr->blocksize) - sizeof(csum); | |
| if (datasz % sizeof(uint32_t)) | |
| return 0; | |
| data = img + le32_to_cpu(hdr->srcaddr); | |
| while (datasz > 0) { | |
| csum += le32_to_cpu(*data++); | |
| datasz -= 4; | |
| } | |
| return cpu_to_le32(csum); | |
| } | |
| static int | |
| kwboot_img_is_secure(void *img) | |
| { | |
| struct opt_hdr_v1 *ohdr; | |
| for_each_opt_hdr_v1 (ohdr, img) | |
| if (ohdr->headertype == OPT_HDR_V1_SECURE_TYPE) | |
| return 1; | |
| return 0; | |
| } | |
| static void * | |
| kwboot_img_grow_data_right(void *img, size_t *size, size_t grow) | |
| { | |
| struct main_hdr_v1 *hdr = img; | |
| void *result; | |
| /* | |
| * 32-bit checksum comes after end of image code, so we will be putting | |
| * new code there. So we get this pointer and then increase data size | |
| * (since increasing data size changes kwboot_img_csum32_ptr() return | |
| * value). | |
| */ | |
| result = kwboot_img_csum32_ptr(img); | |
| hdr->blocksize = cpu_to_le32(le32_to_cpu(hdr->blocksize) + grow); | |
| *size += grow; | |
| return result; | |
| } | |
| static void | |
| kwboot_img_grow_hdr(void *img, size_t *size, size_t grow) | |
| { | |
| uint32_t hdrsz, datasz, srcaddr; | |
| struct main_hdr_v1 *hdr = img; | |
| struct opt_hdr_v1 *ohdr; | |
| uint8_t *data; | |
| srcaddr = le32_to_cpu(hdr->srcaddr); | |
| /* calculate real used space in kwbimage header */ | |
| if (kwbimage_version(img) == 0) { | |
| hdrsz = kwbheader_size(img); | |
| } else { | |
| hdrsz = sizeof(*hdr); | |
| for_each_opt_hdr_v1 (ohdr, hdr) | |
| hdrsz += opt_hdr_v1_size(ohdr); | |
| } | |
| data = (uint8_t *)img + srcaddr; | |
| datasz = *size - srcaddr; | |
| /* only move data if there is not enough space */ | |
| if (hdrsz + grow > srcaddr) { | |
| size_t need = hdrsz + grow - srcaddr; | |
| /* move data by enough bytes */ | |
| memmove(data + need, data, datasz); | |
| hdr->srcaddr = cpu_to_le32(srcaddr + need); | |
| *size += need; | |
| } | |
| if (kwbimage_version(img) == 1) { | |
| hdrsz += grow; | |
| if (hdrsz > kwbheader_size(img)) { | |
| hdr->headersz_msb = hdrsz >> 16; | |
| hdr->headersz_lsb = cpu_to_le16(hdrsz & 0xffff); | |
| } | |
| } | |
| } | |
| static void * | |
| kwboot_add_bin_ohdr_v1(void *img, size_t *size, uint32_t binsz) | |
| { | |
| struct main_hdr_v1 *hdr = img; | |
| struct opt_hdr_v1 *ohdr; | |
| uint32_t num_args; | |
| uint32_t offset; | |
| uint32_t ohdrsz; | |
| uint8_t *prev_ext; | |
| if (hdr->ext) { | |
| for_each_opt_hdr_v1 (ohdr, img) | |
| if (opt_hdr_v1_next(ohdr) == NULL) | |
| break; | |
| prev_ext = opt_hdr_v1_ext(ohdr); | |
| ohdr = _opt_hdr_v1_next(ohdr); | |
| } else { | |
| ohdr = (void *)(hdr + 1); | |
| prev_ext = &hdr->ext; | |
| } | |
| /* | |
| * ARM executable code inside the BIN header on some mvebu platforms | |
| * (e.g. A370, AXP) must always be aligned with the 128-bit boundary. | |
| * This requirement can be met by inserting dummy arguments into | |
| * BIN header, if needed. | |
| */ | |
| offset = &ohdr->data[4] - (char *)img; | |
| num_args = ((16 - offset % 16) % 16) / sizeof(uint32_t); | |
| ohdrsz = sizeof(*ohdr) + 4 + 4 * num_args + binsz + 4; | |
| kwboot_img_grow_hdr(hdr, size, ohdrsz); | |
| *prev_ext = 1; | |
| ohdr->headertype = OPT_HDR_V1_BINARY_TYPE; | |
| ohdr->headersz_msb = ohdrsz >> 16; | |
| ohdr->headersz_lsb = cpu_to_le16(ohdrsz & 0xffff); | |
| memset(&ohdr->data[0], 0, ohdrsz - sizeof(*ohdr)); | |
| *(uint32_t *)&ohdr->data[0] = cpu_to_le32(num_args); | |
| return &ohdr->data[4 + 4 * num_args]; | |
| } | |
| static void | |
| _inject_baudrate_change_code(void *img, size_t *size, int for_data, | |
| int old_baud, int new_baud) | |
| { | |
| struct main_hdr_v1 *hdr = img; | |
| uint32_t orig_datasz; | |
| uint32_t codesz; | |
| uint8_t *code; | |
| if (for_data) { | |
| orig_datasz = le32_to_cpu(hdr->blocksize) - sizeof(uint32_t); | |
| codesz = sizeof(kwboot_baud_code) + | |
| sizeof(kwboot_baud_code_data_jump); | |
| code = kwboot_img_grow_data_right(img, size, codesz); | |
| } else { | |
| codesz = sizeof(kwboot_baud_code_binhdr_pre) + | |
| sizeof(kwboot_baud_code) + | |
| sizeof(kwboot_baud_code_binhdr_post); | |
| code = kwboot_add_bin_ohdr_v1(img, size, codesz); | |
| codesz = sizeof(kwboot_baud_code_binhdr_pre); | |
| memcpy(code, kwboot_baud_code_binhdr_pre, codesz); | |
| code += codesz; | |
| } | |
| codesz = sizeof(kwboot_baud_code) - 2 * sizeof(uint32_t); | |
| memcpy(code, kwboot_baud_code, codesz); | |
| code += codesz; | |
| *(uint32_t *)code = cpu_to_le32(old_baud); | |
| code += sizeof(uint32_t); | |
| *(uint32_t *)code = cpu_to_le32(new_baud); | |
| code += sizeof(uint32_t); | |
| if (for_data) { | |
| codesz = sizeof(kwboot_baud_code_data_jump) - sizeof(uint32_t); | |
| memcpy(code, kwboot_baud_code_data_jump, codesz); | |
| code += codesz; | |
| *(uint32_t *)code = hdr->execaddr; | |
| code += sizeof(uint32_t); | |
| hdr->execaddr = cpu_to_le32(le32_to_cpu(hdr->destaddr) + orig_datasz); | |
| } else { | |
| codesz = sizeof(kwboot_baud_code_binhdr_post); | |
| memcpy(code, kwboot_baud_code_binhdr_post, codesz); | |
| code += codesz; | |
| } | |
| } | |
| static int | |
| kwboot_img_patch(void *img, size_t *size, int baudrate) | |
| { | |
| struct main_hdr_v1 *hdr; | |
| uint32_t srcaddr; | |
| uint8_t csum; | |
| size_t hdrsz; | |
| int image_ver; | |
| int is_secure; | |
| hdr = img; | |
| if (*size < sizeof(struct main_hdr_v1)) | |
| goto err; | |
| image_ver = kwbimage_version(img); | |
| if (image_ver != 0 && image_ver != 1) { | |
| fprintf(stderr, "Invalid image header version\n"); | |
| goto err; | |
| } | |
| hdrsz = kwbheader_size(hdr); | |
| if (*size < hdrsz) | |
| goto err; | |
| csum = kwboot_hdr_csum8(hdr) - hdr->checksum; | |
| if (csum != hdr->checksum) | |
| goto err; | |
| srcaddr = le32_to_cpu(hdr->srcaddr); | |
| switch (hdr->blockid) { | |
| case IBR_HDR_SATA_ID: | |
| if (srcaddr < 1) | |
| goto err; | |
| hdr->srcaddr = cpu_to_le32((srcaddr - 1) * 512); | |
| break; | |
| case IBR_HDR_SDIO_ID: | |
| hdr->srcaddr = cpu_to_le32(srcaddr * 512); | |
| break; | |
| case IBR_HDR_PEX_ID: | |
| if (srcaddr == 0xFFFFFFFF) | |
| hdr->srcaddr = cpu_to_le32(hdrsz); | |
| break; | |
| case IBR_HDR_SPI_ID: | |
| if (hdr->destaddr == cpu_to_le32(0xFFFFFFFF)) { | |
| kwboot_printv("Patching destination and execution addresses from SPI/NOR XIP area to DDR area 0x00800000\n"); | |
| hdr->destaddr = cpu_to_le32(0x00800000); | |
| hdr->execaddr = cpu_to_le32(0x00800000); | |
| } | |
| break; | |
| } | |
| if (hdrsz > le32_to_cpu(hdr->srcaddr) || | |
| *size < le32_to_cpu(hdr->srcaddr) + le32_to_cpu(hdr->blocksize)) | |
| goto err; | |
| if (kwboot_img_csum32(img) != *kwboot_img_csum32_ptr(img)) | |
| goto err; | |
| is_secure = kwboot_img_is_secure(img); | |
| if (hdr->blockid != IBR_HDR_UART_ID) { | |
| if (is_secure) { | |
| fprintf(stderr, | |
| "Image has secure header with signature for non-UART booting\n"); | |
| goto err; | |
| } | |
| kwboot_printv("Patching image boot signature to UART\n"); | |
| hdr->blockid = IBR_HDR_UART_ID; | |
| } | |
| if (!is_secure) { | |
| if (image_ver == 1) { | |
| /* | |
| * Tell BootROM to send BootROM messages to UART port | |
| * number 0 (used also for UART booting) with default | |
| * baudrate (which should be 115200) and do not touch | |
| * UART MPP configuration. | |
| */ | |
| hdr->flags |= 0x1; | |
| hdr->options &= ~0x1F; | |
| hdr->options |= MAIN_HDR_V1_OPT_BAUD_DEFAULT; | |
| hdr->options |= 0 << 3; | |
| } | |
| if (image_ver == 0) | |
| ((struct main_hdr_v0 *)img)->nandeccmode = IBR_HDR_ECC_DISABLED; | |
| hdr->nandpagesize = 0; | |
| } | |
| if (baudrate) { | |
| if (image_ver == 0) { | |
| fprintf(stderr, | |
| "Cannot inject code for changing baudrate into v0 image header\n"); | |
| goto err; | |
| } | |
| if (is_secure) { | |
| fprintf(stderr, | |
| "Cannot inject code for changing baudrate into image with secure header\n"); | |
| goto err; | |
| } | |
| /* | |
| * First inject code that changes the baudrate from the default | |
| * value of 115200 Bd to requested value. This code is inserted | |
| * as a new opt hdr, so it is executed by BootROM after the | |
| * header part is received. | |
| */ | |
| kwboot_printv("Injecting binary header code for changing baudrate to %d Bd\n", | |
| baudrate); | |
| _inject_baudrate_change_code(img, size, 0, 115200, baudrate); | |
| /* | |
| * Now inject code that changes the baudrate back to 115200 Bd. | |
| * This code is appended after the data part of the image, and | |
| * execaddr is changed so that it is executed before U-Boot | |
| * proper. | |
| */ | |
| kwboot_printv("Injecting code for changing baudrate back\n"); | |
| _inject_baudrate_change_code(img, size, 1, baudrate, 115200); | |
| /* Update the 32-bit data checksum */ | |
| *kwboot_img_csum32_ptr(img) = kwboot_img_csum32(img); | |
| /* recompute header size */ | |
| hdrsz = kwbheader_size(hdr); | |
| } | |
| if (hdrsz % KWBOOT_XM_BLKSZ) { | |
| size_t grow = KWBOOT_XM_BLKSZ - hdrsz % KWBOOT_XM_BLKSZ; | |
| if (is_secure) { | |
| fprintf(stderr, "Cannot align image with secure header\n"); | |
| goto err; | |
| } | |
| kwboot_printv("Aligning image header to Xmodem block size\n"); | |
| kwboot_img_grow_hdr(img, size, grow); | |
| } | |
| hdr->checksum = kwboot_hdr_csum8(hdr) - csum; | |
| *size = le32_to_cpu(hdr->srcaddr) + le32_to_cpu(hdr->blocksize); | |
| return 0; | |
| err: | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| static void | |
| kwboot_usage(FILE *stream, char *progname) | |
| { | |
| fprintf(stream, | |
| "Usage: %s [OPTIONS] [-b <image> | -D <image> | -b | -d ] [-B <baud> ] [-t] <TTY>\n", | |
| progname); | |
| fprintf(stream, "\n"); | |
| fprintf(stream, | |
| " -b <image>: boot <image> with preamble (Kirkwood, Avanta, Armada 370/XP/375/38x/39x)\n"); | |
| fprintf(stream, | |
| " -D <image>: boot <image> without preamble (Dove)\n"); | |
| fprintf(stream, " -b: enter xmodem boot mode\n"); | |
| fprintf(stream, " -d: enter console debug mode\n"); | |
| fprintf(stream, " -a: use timings for Armada XP\n"); | |
| fprintf(stream, " -s <resp-timeo>: use specific response-timeout\n"); | |
| fprintf(stream, | |
| " -o <block-timeo>: use specific xmodem block timeout\n"); | |
| fprintf(stream, "\n"); | |
| fprintf(stream, " -t: mini terminal\n"); | |
| fprintf(stream, "\n"); | |
| fprintf(stream, " -B <baud>: set baud rate\n"); | |
| fprintf(stream, "\n"); | |
| } | |
| int | |
| main(int argc, char **argv) | |
| { | |
| const char *ttypath, *imgpath; | |
| int rv, rc, tty, term; | |
| int bootmsg; | |
| int debugmsg; | |
| void *img; | |
| size_t size; | |
| size_t after_img_rsv; | |
| int baudrate; | |
| int prev_optind; | |
| int c; | |
| rv = 1; | |
| tty = -1; | |
| bootmsg = 0; | |
| debugmsg = 0; | |
| imgpath = NULL; | |
| img = NULL; | |
| term = 0; | |
| size = 0; | |
| after_img_rsv = KWBOOT_XM_BLKSZ; | |
| baudrate = 115200; | |
| printf("kwboot version %s\n", PLAIN_VERSION); | |
| kwboot_verbose = isatty(STDOUT_FILENO); | |
| do { | |
| prev_optind = optind; | |
| c = getopt(argc, argv, "hbptaB:dD:q:s:o:"); | |
| if (c < 0) | |
| break; | |
| switch (c) { | |
| case 'b': | |
| if (imgpath || bootmsg || debugmsg) | |
| goto usage; | |
| bootmsg = 1; | |
| if (prev_optind == optind) | |
| goto usage; | |
| /* Option -b could have optional argument which specify image path */ | |
| if (optind < argc && argv[optind] && argv[optind][0] != '-') | |
| imgpath = argv[optind++]; | |
| break; | |
| case 'D': | |
| if (imgpath || bootmsg || debugmsg) | |
| goto usage; | |
| bootmsg = 0; | |
| imgpath = optarg; | |
| break; | |
| case 'd': | |
| if (imgpath || bootmsg || debugmsg) | |
| goto usage; | |
| debugmsg = 1; | |
| break; | |
| case 'p': | |
| /* nop, for backward compatibility */ | |
| break; | |
| case 't': | |
| term = 1; | |
| break; | |
| case 'a': | |
| msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO_AXP; | |
| break; | |
| case 'q': | |
| /* nop, for backward compatibility */ | |
| break; | |
| case 's': | |
| msg_rsp_timeo = atoi(optarg); | |
| break; | |
| case 'o': | |
| blk_rsp_timeo = atoi(optarg); | |
| break; | |
| case 'B': | |
| baudrate = atoi(optarg); | |
| break; | |
| case 'h': | |
| rv = 0; | |
| default: | |
| goto usage; | |
| } | |
| } while (1); | |
| if (!bootmsg && !term && !debugmsg && !imgpath) | |
| goto usage; | |
| /* | |
| * If there is no remaining argument but optional imgpath was parsed | |
| * then it means that optional imgpath was eaten by getopt parser. | |
| * Reassing imgpath to required ttypath argument. | |
| */ | |
| if (optind == argc && imgpath) { | |
| ttypath = imgpath; | |
| imgpath = NULL; | |
| } else if (optind + 1 == argc) { | |
| ttypath = argv[optind]; | |
| } else { | |
| goto usage; | |
| } | |
| /* boot and debug message use baudrate 115200 */ | |
| if (((bootmsg && !imgpath) || debugmsg) && baudrate != 115200) { | |
| fprintf(stderr, "Baudrate other than 115200 cannot be used for this operation.\n"); | |
| goto usage; | |
| } | |
| tty = kwboot_open_tty(ttypath, baudrate); | |
| if (tty < 0) { | |
| perror(ttypath); | |
| goto out; | |
| } | |
| /* | |
| * initial baudrate for image transfer is always 115200, | |
| * the change to different baudrate is done only after the header is sent | |
| */ | |
| if (imgpath && baudrate != 115200) { | |
| rc = kwboot_tty_change_baudrate(tty, 115200); | |
| if (rc) { | |
| perror(ttypath); | |
| goto out; | |
| } | |
| } | |
| if (baudrate == 115200) | |
| /* do not change baudrate during Xmodem to the same value */ | |
| baudrate = 0; | |
| else | |
| /* ensure we have enough space for baudrate change code */ | |
| after_img_rsv += sizeof(struct opt_hdr_v1) + 8 + 16 + | |
| sizeof(kwboot_baud_code_binhdr_pre) + | |
| sizeof(kwboot_baud_code) + | |
| sizeof(kwboot_baud_code_binhdr_post) + | |
| KWBOOT_XM_BLKSZ + | |
| sizeof(kwboot_baud_code) + | |
| sizeof(kwboot_baud_code_data_jump) + | |
| KWBOOT_XM_BLKSZ; | |
| if (imgpath) { | |
| img = kwboot_read_image(imgpath, &size, after_img_rsv); | |
| if (!img) { | |
| perror(imgpath); | |
| goto out; | |
| } | |
| rc = kwboot_img_patch(img, &size, baudrate); | |
| if (rc) { | |
| fprintf(stderr, "%s: Invalid image.\n", imgpath); | |
| goto out; | |
| } | |
| } | |
| if (debugmsg) { | |
| rc = kwboot_debugmsg(tty); | |
| if (rc) | |
| goto out; | |
| } else if (bootmsg) { | |
| rc = kwboot_bootmsg(tty); | |
| if (rc) | |
| goto out; | |
| } | |
| if (img) { | |
| rc = kwboot_xmodem(tty, img, size, baudrate); | |
| if (rc) { | |
| perror("xmodem"); | |
| goto out; | |
| } | |
| } | |
| if (term) { | |
| rc = kwboot_terminal(tty); | |
| if (rc && !(errno == EINTR)) { | |
| perror("terminal"); | |
| goto out; | |
| } | |
| } | |
| rv = 0; | |
| out: | |
| if (tty >= 0) | |
| close(tty); | |
| if (img) | |
| free(img); | |
| return rv; | |
| usage: | |
| kwboot_usage(rv ? stderr : stdout, basename(argv[0])); | |
| goto out; | |
| } |