/* * Milkymist SoC * Copyright (C) 2007, 2008, 2009, 2010, 2011 Sebastien Bourdeauducq * Copyright (C) 2011 Michael Walle * Copyright (C) 2004 MontaVista Software, Inc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif #define DEFAULT_KERNELADR (0x40000000) #define DEFAULT_CMDLINEADR (0x41000000) #define DEFAULT_INITRDADR (0x41002000) #define GDBBUFLEN 1000 unsigned int crc16_table[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; static int debug = 0; static unsigned short crc16(const void *_buffer, int len) { const unsigned char *buffer = (const unsigned char *)_buffer; unsigned short crc; crc = 0; while(len-- > 0) crc = crc16_table[((crc >> 8) ^ (*buffer++)) & 0xFF] ^ (crc << 8); return crc; } static int write_exact(int fd, const char *data, unsigned int length) { int r; while(length > 0) { r = write(fd, data, length); if(r <= 0) return 0; length -= r; data += r; } return 1; } /* length, cmd and payload must be filled in */ static int send_frame(int serialfd, struct sfl_frame *frame) { unsigned short int crc; int retry; char reply; crc = crc16(&frame->cmd, frame->length+1); frame->crc[0] = (crc & 0xff00) >> 8; frame->crc[1] = (crc & 0x00ff); retry = 0; do { if(!write_exact(serialfd, (char *)frame, frame->length+4)) { perror("[FLTERM] Unable to write to serial port."); return 0; } /* Get the reply from the device */ read(serialfd, &reply, 1); /* TODO: timeout */ switch(reply) { case SFL_ACK_SUCCESS: retry = 0; break; case SFL_ACK_CRCERROR: retry = 1; break; default: fprintf(stderr, "[FLTERM] Got unknown reply '%c' from the device, aborting.\n", reply); return 0; } } while(retry); return 1; } static int upload_fd(int serialfd, const char *name, int firmwarefd, unsigned int load_address) { struct sfl_frame frame; int readbytes; int length; int position; unsigned int current_address; struct timeval t0; struct timeval t1; int millisecs; length = lseek(firmwarefd, 0, SEEK_END); lseek(firmwarefd, 0, SEEK_SET); printf("[FLTERM] Uploading %s (%d bytes)...\n", name, length); gettimeofday(&t0, NULL); current_address = load_address; position = 0; while(1) { printf("%d%%\r", 100*position/length); fflush(stdout); readbytes = read(firmwarefd, &frame.payload[4], sizeof(frame.payload) - 4); if(readbytes < 0) { perror("[FLTERM] Unable to read image."); return -1; } if(readbytes == 0) break; frame.length = readbytes+4; frame.cmd = SFL_CMD_LOAD; frame.payload[0] = (current_address & 0xff000000) >> 24; frame.payload[1] = (current_address & 0x00ff0000) >> 16; frame.payload[2] = (current_address & 0x0000ff00) >> 8; frame.payload[3] = (current_address & 0x000000ff); if(!send_frame(serialfd, &frame)) return -1; current_address += readbytes; position += readbytes; } gettimeofday(&t1, NULL); millisecs = (t1.tv_sec - t0.tv_sec)*1000 + (t1.tv_usec - t0.tv_usec)/1000; printf("[FLTERM] Upload complete (%.1fKB/s).\n", 1000.0*(double)length/((double)millisecs*1024.0)); return length; } static const char sfl_magic_req[SFL_MAGIC_LEN] = SFL_MAGIC_REQ; static const char sfl_magic_ack[SFL_MAGIC_LEN] = SFL_MAGIC_ACK; static void answer_magic(int serialfd, const char *kernel_image, unsigned int kernel_address, const char *cmdline, unsigned int cmdline_address, const char *initrd_image, unsigned int initrd_address) { int kernelfd, initrdfd; struct sfl_frame frame; printf("[FLTERM] Received firmware download request from the device.\n"); kernelfd = open(kernel_image, O_RDONLY); if(kernelfd == -1) { perror("[FLTERM] Unable to open kernel image (request ignored)."); return; } initrdfd = -1; if(initrd_image != NULL) { initrdfd = open(initrd_image, O_RDONLY); if(initrdfd == -1) { perror("[FLTERM] Unable to open initrd image (request ignored)."); close(kernelfd); return; } } write_exact(serialfd, sfl_magic_ack, SFL_MAGIC_LEN); upload_fd(serialfd, "kernel", kernelfd, kernel_address); if(cmdline != NULL) { int len; printf("[FLTERM] Setting kernel command line: '%s'.\n", cmdline); len = strlen(cmdline)+1; if(len > (254-4)) { fprintf(stderr, "[FLTERM] Kernel command line too long, load aborted.\n"); close(initrdfd); close(kernelfd); return; } frame.length = len+4; frame.cmd = SFL_CMD_LOAD; frame.payload[0] = (cmdline_address & 0xff000000) >> 24; frame.payload[1] = (cmdline_address & 0x00ff0000) >> 16; frame.payload[2] = (cmdline_address & 0x0000ff00) >> 8; frame.payload[3] = (cmdline_address & 0x000000ff); strcpy((char *)&frame.payload[4], cmdline); send_frame(serialfd, &frame); frame.length = 4; frame.cmd = SFL_CMD_CMDLINE; frame.payload[0] = (cmdline_address & 0xff000000) >> 24; frame.payload[1] = (cmdline_address & 0x00ff0000) >> 16; frame.payload[2] = (cmdline_address & 0x0000ff00) >> 8; frame.payload[3] = (cmdline_address & 0x000000ff); send_frame(serialfd, &frame); } if(initrdfd != -1) { int len; len = upload_fd(serialfd, "initrd", initrdfd, initrd_address); if(len <= 0) return; frame.length = 4; frame.cmd = SFL_CMD_INITRDSTART; frame.payload[0] = (initrd_address & 0xff000000) >> 24; frame.payload[1] = (initrd_address & 0x00ff0000) >> 16; frame.payload[2] = (initrd_address & 0x0000ff00) >> 8; frame.payload[3] = (initrd_address & 0x000000ff); send_frame(serialfd, &frame); initrd_address += len-1; frame.length = 4; frame.cmd = SFL_CMD_INITRDEND; frame.payload[0] = (initrd_address & 0xff000000) >> 24; frame.payload[1] = (initrd_address & 0x00ff0000) >> 16; frame.payload[2] = (initrd_address & 0x0000ff00) >> 8; frame.payload[3] = (initrd_address & 0x000000ff); send_frame(serialfd, &frame); } /* Send the jump command */ printf("[FLTERM] Booting the device.\n"); frame.length = 4; frame.cmd = SFL_CMD_JUMP; frame.payload[0] = (kernel_address & 0xff000000) >> 24; frame.payload[1] = (kernel_address & 0x00ff0000) >> 16; frame.payload[2] = (kernel_address & 0x0000ff00) >> 8; frame.payload[3] = (kernel_address & 0x000000ff); if(!send_frame(serialfd, &frame)) return; printf("[FLTERM] Done.\n"); close(initrdfd); close(kernelfd); } static int hex(unsigned char c) { if(c >= 'a' && c <= 'f') { return c - 'a' + 10; } if(c >= '0' && c <= '9') { return c - '0'; } if(c >= 'A' && c <= 'F') { return c - 'A' + 10; } return 0; } /* * This is taken from kdmx2. * Author: Tom Rini */ static void gdb_process_packet(int infd, int outfd, int altfd) { /* gdb packet handling */ char gdbbuf[GDBBUFLEN + 1]; int pos = 0; unsigned char runcksum = 0; unsigned char recvcksum = 0; struct pollfd fds; char c; int seen_hash = 0; fds.fd = infd; fds.events = POLLIN; memset(gdbbuf, 0, sizeof(gdbbuf)); gdbbuf[0] = '$'; pos++; while (1) { fds.revents = 0; if(poll(&fds, 1, 100) == 0) { /* timeout */ if(altfd != -1) { write(altfd, gdbbuf, pos); } break; } if(pos == GDBBUFLEN) { if(altfd != -1) { write(altfd, gdbbuf, pos); } break; } read(infd, &c, 1); gdbbuf[pos++] = c; if(c == '#') { seen_hash = 1; } else if(seen_hash == 0) { runcksum += c; } else if(seen_hash == 1) { recvcksum = hex(c) << 4; seen_hash = 2; } else if(seen_hash == 2) { recvcksum |= hex(c); seen_hash = 3; } if(seen_hash == 3) { /* we're done */ runcksum %= 256; if(recvcksum == runcksum) { if(debug) { fprintf(stderr, "[GDB %s]\n", gdbbuf); } write(outfd, gdbbuf, pos); } else { if(altfd != -1) { write(altfd, gdbbuf, pos); } } seen_hash = 0; break; } } } static void do_terminal(char *serial_port, int baud, int gdb_passthrough, const char *kernel_image, unsigned int kernel_address, const char *cmdline, unsigned int cmdline_address, const char *initrd_image, unsigned int initrd_address, char *log_path) { int serialfd; int gdbfd = -1; FILE *logfd = NULL; struct termios my_termios; char c; int recognized; struct pollfd fds[3]; int flags; int rsp_pending = 0; int c_cflag; int custom_divisor; /* Open and configure the serial port */ if(log_path != NULL) { logfd = fopen(log_path, "a+"); if(logfd == NULL) { perror("Unable to open log file"); return; } } serialfd = open(serial_port, O_RDWR|O_NOCTTY); if(serialfd == -1) { perror("Unable to open serial port"); return; } custom_divisor = 0; switch(baud) { case 9600: c_cflag = B9600; break; case 19200: c_cflag = B19200; break; case 38400: c_cflag = B38400; break; case 57600: c_cflag = B57600; break; case 115200: c_cflag = B115200; break; case 230400: c_cflag = B230400; break; default: c_cflag = B38400; custom_divisor = 1; break; } #ifdef __linux__ if(custom_divisor) { struct serial_struct serial_info; ioctl(serialfd, TIOCGSERIAL, &serial_info); serial_info.custom_divisor = serial_info.baud_base / baud; serial_info.flags &= ~ASYNC_SPD_MASK; serial_info.flags |= ASYNC_SPD_CUST; ioctl(serialfd, TIOCSSERIAL, &serial_info); } #else if(custom_divisor) { fprintf(stderr, "[FLTERM] baudrate not supported\n"); return; } #endif /* Thanks to Julien Schmitt (GTKTerm) for figuring out the correct parameters * to put into that weird struct. */ tcgetattr(serialfd, &my_termios); my_termios.c_cflag = c_cflag; my_termios.c_cflag |= CS8; my_termios.c_cflag |= CREAD; my_termios.c_iflag = IGNPAR | IGNBRK; my_termios.c_cflag |= CLOCAL; my_termios.c_oflag = 0; my_termios.c_lflag = 0; my_termios.c_cc[VTIME] = 0; my_termios.c_cc[VMIN] = 1; tcsetattr(serialfd, TCSANOW, &my_termios); tcflush(serialfd, TCOFLUSH); tcflush(serialfd, TCIFLUSH); /* Prepare the fdset for poll() */ fds[0].fd = 0; fds[0].events = POLLIN; fds[1].fd = serialfd; fds[1].events = POLLIN; recognized = 0; flags = fcntl(serialfd, F_GETFL, 0); while(1) { if(gdbfd == -1 && gdb_passthrough) { gdbfd = open("/dev/ptmx", O_RDWR); if(grantpt(gdbfd) != 0) { perror("grantpt()"); return; } if(unlockpt(gdbfd) != 0) { perror("unlockpt()"); return; } printf("[GDB passthrough] use %s as GDB remote device\n", ptsname(gdbfd)); fds[2].fd = gdbfd; fds[2].events = POLLIN; } fds[0].revents = 0; fds[1].revents = 0; fds[2].revents = 0; /* poll() behaves strangely when the serial port descriptor is in * blocking mode. So work around this. */ fcntl(serialfd, F_SETFL, flags|O_NONBLOCK); if(poll(&fds[0], (gdbfd == -1) ? 2 : 3, -1) < 0) break; fcntl(serialfd, F_SETFL, flags); if(fds[0].revents & POLLIN) { if(read(0, &c, 1) <= 0) break; if(write(serialfd, &c, 1) <= 0) break; } if(fds[2].revents & POLLIN) { rsp_pending = 1; if(read(gdbfd, &c, 1) <= 0) break; if(c == '\03') { /* convert ETX to breaks */ if(debug) { fprintf(stderr, "[GDB BREAK]\n"); } tcsendbreak(serialfd, 0); } else if(c == '$') { gdb_process_packet(gdbfd, serialfd, -1); } else if(c == '+' || c == '-') { write(serialfd, &c, 1); } else { fprintf(stderr, "Internal error (line %d)", __LINE__); exit(1); } } if(fds[2].revents & POLLHUP) { /* close and reopen new pair */ close(gdbfd); gdbfd = -1; continue; } if(fds[1].revents & POLLIN) { if(read(serialfd, &c, 1) <= 0) break; if(logfd && c && isascii(c)) { fwrite(&c, sizeof(c), 1, logfd); if(c == '\n') fflush(logfd); } if(gdbfd != -1 && rsp_pending && (c == '+' || c == '-')) { rsp_pending = 0; write(gdbfd, &c, 1); } else if(gdbfd != -1 && c == '$') { gdb_process_packet(serialfd, gdbfd, 0); } else { /* write to terminal */ write(0, &c, 1); if(kernel_image != NULL) { if(c == sfl_magic_req[recognized]) { recognized++; if(recognized == SFL_MAGIC_LEN) { /* We've got the magic string ! */ recognized = 0; answer_magic(serialfd, kernel_image, kernel_address, cmdline, cmdline_address, initrd_image, initrd_address); } } else { if(c == sfl_magic_req[0]) recognized = 1; else recognized = 0; } } } } } close(serialfd); if(gdbfd != -1) close(gdbfd); if(logfd) fclose(logfd); } enum { OPTION_PORT, OPTION_GDB_PASSTHROUGH, OPTION_SPEED, OPTION_DEBUG, OPTION_KERNEL, OPTION_KERNELADR, OPTION_CMDLINE, OPTION_CMDLINEADR, OPTION_INITRD, OPTION_INITRDADR, OPTION_LOG }; static const struct option options[] = { { .name = "port", .has_arg = 1, .val = OPTION_PORT }, { .name = "gdb-passthrough", .has_arg = 0, .val = OPTION_GDB_PASSTHROUGH }, { .name = "debug", .has_arg = 0, .val = OPTION_DEBUG }, { .name = "speed", .has_arg = 1, .val = OPTION_SPEED }, { .name = "kernel", .has_arg = 1, .val = OPTION_KERNEL }, { .name = "kernel-adr", .has_arg = 1, .val = OPTION_KERNELADR }, { .name = "cmdline", .has_arg = 1, .val = OPTION_CMDLINE }, { .name = "cmdline-adr", .has_arg = 1, .val = OPTION_CMDLINEADR }, { .name = "initrd", .has_arg = 1, .val = OPTION_INITRD }, { .name = "initrd-adr", .has_arg = 1, .val = OPTION_INITRDADR }, { .name = "log", .has_arg = 1, .val = OPTION_LOG }, { .name = NULL } }; static void print_usage() { fprintf(stderr, "Serial boot program for Milkymist SoC - v. 2.3\n"); fprintf(stderr, "Copyright (C) 2007, 2008, 2009, 2010, 2011 Sebastien Bourdeauducq\n"); fprintf(stderr, "Copyright (C) 2011 Michael Walle\n"); fprintf(stderr, "Copyright (C) 2004 MontaVista Software, Inc\n\n"); fprintf(stderr, "This program is free software: you can redistribute it and/or modify\n"); fprintf(stderr, "it under the terms of the GNU General Public License as published by\n"); fprintf(stderr, "the Free Software Foundation, version 3 of the License.\n\n"); fprintf(stderr, "Usage: flterm --port \n"); fprintf(stderr, " [--speed ] [--gdb-passthrough] [--debug]\n"); fprintf(stderr, " [--kernel [--kernel-adr
]]\n"); fprintf(stderr, " [--cmdline [--cmdline-adr
]]\n"); fprintf(stderr, " [--initrd [--initrd-adr
]]\n"); fprintf(stderr, " [--log ]\n\n"); fprintf(stderr, "Default load addresses:\n"); fprintf(stderr, " kernel: 0x%08x\n", DEFAULT_KERNELADR); fprintf(stderr, " cmdline: 0x%08x\n", DEFAULT_CMDLINEADR); fprintf(stderr, " initrd: 0x%08x\n", DEFAULT_INITRDADR); } int main(int argc, char *argv[]) { int opt; char *serial_port; int baud; int gdb_passthrough; char *kernel_image; unsigned int kernel_address; char *cmdline; unsigned int cmdline_address; char *initrd_image; unsigned int initrd_address; char *endptr; char *log_path; struct termios otty, ntty; /* Fetch command line arguments */ serial_port = NULL; baud = 115200; gdb_passthrough = 0; kernel_image = NULL; kernel_address = DEFAULT_KERNELADR; cmdline = NULL; cmdline_address = DEFAULT_CMDLINEADR; initrd_image = NULL; initrd_address = DEFAULT_INITRDADR; log_path = NULL; while((opt = getopt_long(argc, argv, "", options, NULL)) != -1) { if(opt == '?') { print_usage(); return 1; } switch(opt) { case OPTION_PORT: free(serial_port); serial_port = strdup(optarg); break; case OPTION_SPEED: baud = strtoul(optarg, &endptr, 0); if(*endptr != 0) { fprintf(stderr, "[FLTERM] Couldn't parse baudrate\n"); return 1; } break; case OPTION_DEBUG: debug = 1; break; case OPTION_GDB_PASSTHROUGH: gdb_passthrough = 1; break; case OPTION_KERNEL: free(kernel_image); kernel_image = strdup(optarg); break; case OPTION_KERNELADR: kernel_address = strtoul(optarg, &endptr, 0); if(*endptr != 0) { fprintf(stderr, "[FLTERM] Couldn't parse kernel address\n"); return 1; } break; case OPTION_CMDLINE: free(cmdline); cmdline = strdup(optarg); break; case OPTION_CMDLINEADR: cmdline_address = strtoul(optarg, &endptr, 0); if(*endptr != 0) { fprintf(stderr, "[FLTERM] Couldn't parse cmdline address\n"); return 1; } break; case OPTION_INITRD: free(initrd_image); initrd_image = strdup(optarg); break; case OPTION_INITRDADR: initrd_address = strtoul(optarg, &endptr, 0); if(*endptr != 0) { fprintf(stderr, "[FLTERM] Couldn't parse initrd address\n"); return 1; } break; case OPTION_LOG: free(log_path); log_path = strdup(optarg); break; } } if(serial_port == NULL) { fprintf(stderr, "[FLTERM] No port given\n"); return 1; } /* Banner */ printf("[FLTERM] Starting...\n"); /* Set up stdin/out */ tcgetattr(0, &otty); ntty = otty; ntty.c_lflag &= ~(ECHO | ICANON); tcsetattr(0, TCSANOW, &ntty); /* Do the bulk of the work */ do_terminal(serial_port, baud, gdb_passthrough, kernel_image, kernel_address, cmdline, cmdline_address, initrd_image, initrd_address, log_path); /* Restore stdin/out into their previous state */ tcsetattr(0, TCSANOW, &otty); return 0; }