MIDI
[mw/milkymist.git] / tools / flterm.c
1 /*
2  * Milkymist VJ SoC
3  * Copyright (C) 2007, 2008, 2009 Sebastien Bourdeauducq
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3 of the License.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/ioctl.h>
23 #include <sys/time.h>
24 #include <string.h>
25 #include <termios.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <poll.h>
31 #include <fcntl.h>
32 #include <getopt.h>
33 #include <sfl.h>
34
35 #define DEFAULT_KERNELADR       (0x40000000)
36 #define DEFAULT_CMDLINEADR      (0x41000000)
37 #define DEFAULT_INITRDADR       (0x41002000)
38
39 unsigned int crc16_table[256] = {
40         0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
41         0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
42         0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
43         0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
44         0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
45         0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
46         0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
47         0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
48         0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
49         0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
50         0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
51         0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
52         0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
53         0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
54         0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
55         0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
56         0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
57         0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
58         0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
59         0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
60         0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
61         0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
62         0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
63         0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
64         0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
65         0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
66         0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
67         0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
68         0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
69         0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
70         0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
71         0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
72 };
73
74 static unsigned short crc16(const void *_buffer, int len)
75 {
76         const unsigned char *buffer = (const unsigned char *)_buffer;
77         unsigned short crc;
78         
79         crc = 0;
80         while(len-- > 0)
81             crc = crc16_table[((crc >> 8) ^ (*buffer++)) & 0xFF] ^ (crc << 8);
82         
83         return crc;
84 }
85
86 static int write_exact(int fd, const char *data, unsigned int length)
87 {
88         int r;
89         
90         while(length > 0) {
91                 r = write(fd, data, length);
92                 if(r <= 0) return 0;
93                 length -= r;
94                 data += r;
95         }
96         return 1;
97 }
98
99 /* length, cmd and payload must be filled in */
100 static int send_frame(int serialfd, struct sfl_frame *frame)
101 {
102         unsigned short int crc;
103         int retry;
104         char reply;
105         
106         crc = crc16(&frame->cmd, frame->length+1);
107         frame->crc[0] = (crc & 0xff00) >> 8;
108         frame->crc[1] = (crc & 0x00ff);
109         
110         retry = 0;
111         do {
112                 if(!write_exact(serialfd, (char *)frame, frame->length+4)) {
113                         perror("[FLTERM] Unable to write to serial port.");
114                         return 0;
115                 }
116                 /* Get the reply from the device */
117                 read(serialfd, &reply, 1); /* TODO: timeout */
118                 switch(reply) {
119                         case SFL_ACK_SUCCESS:
120                                 retry = 0;
121                                 break;
122                         case SFL_ACK_CRCERROR:
123                                 retry = 1;
124                                 break;
125                         default:
126                                 fprintf(stderr, "[FLTERM] Got unknown reply '%c' from the device, aborting.\n", reply);
127                                 return 0;
128                 }
129         } while(retry);
130         return 1;
131 }
132
133 static int upload_fd(int serialfd, const char *name, int firmwarefd, unsigned int load_address)
134 {
135         struct sfl_frame frame;
136         int readbytes;
137         int length;
138         int position;
139         unsigned int current_address;
140         struct timeval t0;
141         struct timeval t1;
142         int millisecs;
143         
144         length = lseek(firmwarefd, 0, SEEK_END);
145         lseek(firmwarefd, 0, SEEK_SET);
146         
147         printf("[FLTERM] Uploading %s (%d bytes)...\n", name, length);
148         
149         gettimeofday(&t0, NULL);
150         
151         current_address = load_address;
152         position = 0;
153         while(1) {
154                 printf("%d%%\r", 100*position/length);
155                 fflush(stdout);
156         
157                 readbytes = read(firmwarefd, &frame.payload[4], sizeof(frame.payload) - 4);
158                 if(readbytes < 0) {
159                         perror("[FLTERM] Unable to read image.");
160                         return -1;
161                 }
162                 if(readbytes == 0) break;
163                 
164                 frame.length = readbytes+4;
165                 frame.cmd = SFL_CMD_LOAD;
166                 frame.payload[0] = (current_address & 0xff000000) >> 24;
167                 frame.payload[1] = (current_address & 0x00ff0000) >> 16;
168                 frame.payload[2] = (current_address & 0x0000ff00) >> 8;
169                 frame.payload[3] = (current_address & 0x000000ff);
170                 
171                 if(!send_frame(serialfd, &frame)) return -1;
172                 
173                 current_address += readbytes;
174                 position += readbytes;
175         }
176         
177         gettimeofday(&t1, NULL);
178         
179         millisecs = (t1.tv_sec - t0.tv_sec)*1000 + (t1.tv_usec - t0.tv_usec)/1000;
180         
181         printf("[FLTERM] Upload complete (%.1fKB/s).\n", 1000.0*(double)length/((double)millisecs*1024.0));
182         return length;
183 }
184
185 static const char sfl_magic_req[SFL_MAGIC_LEN] = SFL_MAGIC_REQ;
186 static const char sfl_magic_ack[SFL_MAGIC_LEN] = SFL_MAGIC_ACK;
187
188 static void answer_magic(int serialfd,
189         const char *kernel_image, unsigned int kernel_address,
190         const char *cmdline, unsigned int cmdline_address,
191         const char *initrd_image, unsigned int initrd_address)
192 {
193         int kernelfd, initrdfd;
194         struct sfl_frame frame;
195         
196         printf("[FLTERM] Received firmware download request from the device.\n");
197         
198         kernelfd = open(kernel_image, O_RDONLY);
199         if(kernelfd == -1) {
200                 perror("[FLTERM] Unable to open kernel image (request ignored).");
201                 return;
202         }
203         initrdfd = -1;
204         if(initrd_image != NULL) {
205                 initrdfd = open(initrd_image, O_RDONLY);
206                 if(initrdfd == -1) {
207                         perror("[FLTERM] Unable to open initrd image (request ignored).");
208                         close(kernelfd);
209                         return;
210                 }
211         }
212
213         write_exact(serialfd, sfl_magic_ack, SFL_MAGIC_LEN);
214         
215         upload_fd(serialfd, "kernel", kernelfd, kernel_address);
216         if(cmdline != NULL) {
217                 int len;
218
219                 printf("[FLTERM] Setting kernel command line: '%s'.\n", cmdline);
220
221                 len = strlen(cmdline)+1;
222                 if(len > (254-4)) {
223                         fprintf(stderr, "[FLTERM] Kernel command line too long, load aborted.\n");
224                         close(initrdfd);
225                         close(kernelfd);
226                         return;
227                 }
228                 frame.length = len+4;
229                 frame.cmd = SFL_CMD_LOAD;
230                 frame.payload[0] = (cmdline_address & 0xff000000) >> 24;
231                 frame.payload[1] = (cmdline_address & 0x00ff0000) >> 16;
232                 frame.payload[2] = (cmdline_address & 0x0000ff00) >> 8;
233                 frame.payload[3] = (cmdline_address & 0x000000ff);
234                 strcpy((char *)&frame.payload[4], cmdline);
235                 send_frame(serialfd, &frame);
236
237                 frame.length = 4;
238                 frame.cmd = SFL_CMD_CMDLINE;
239                 frame.payload[0] = (cmdline_address & 0xff000000) >> 24;
240                 frame.payload[1] = (cmdline_address & 0x00ff0000) >> 16;
241                 frame.payload[2] = (cmdline_address & 0x0000ff00) >> 8;
242                 frame.payload[3] = (cmdline_address & 0x000000ff);
243                 send_frame(serialfd, &frame);
244         }
245         if(initrdfd != -1) {
246                 int len;
247                 
248                 len = upload_fd(serialfd, "initrd", initrdfd, initrd_address);
249                 if(len <= 0) return;
250                 
251                 frame.length = 4;
252                 frame.cmd = SFL_CMD_INITRDSTART;
253                 frame.payload[0] = (initrd_address & 0xff000000) >> 24;
254                 frame.payload[1] = (initrd_address & 0x00ff0000) >> 16;
255                 frame.payload[2] = (initrd_address & 0x0000ff00) >> 8;
256                 frame.payload[3] = (initrd_address & 0x000000ff);
257                 send_frame(serialfd, &frame);
258
259                 initrd_address += len-1;
260
261                 frame.length = 4;
262                 frame.cmd = SFL_CMD_INITRDEND;
263                 frame.payload[0] = (initrd_address & 0xff000000) >> 24;
264                 frame.payload[1] = (initrd_address & 0x00ff0000) >> 16;
265                 frame.payload[2] = (initrd_address & 0x0000ff00) >> 8;
266                 frame.payload[3] = (initrd_address & 0x000000ff);
267                 send_frame(serialfd, &frame);
268         }
269
270         /* Send the jump command */
271         printf("[FLTERM] Booting the device.\n");
272         frame.length = 4;
273         frame.cmd = SFL_CMD_JUMP;
274         frame.payload[0] = (kernel_address & 0xff000000) >> 24;
275         frame.payload[1] = (kernel_address & 0x00ff0000) >> 16;
276         frame.payload[2] = (kernel_address & 0x0000ff00) >> 8;
277         frame.payload[3] = (kernel_address & 0x000000ff);
278         if(!send_frame(serialfd, &frame)) return;
279
280         printf("[FLTERM] Done.\n");
281
282         close(initrdfd);
283         close(kernelfd);
284 }
285
286 static void do_terminal(char *serial_port,
287         int doublerate,
288         const char *kernel_image, unsigned int kernel_address,
289         const char *cmdline, unsigned int cmdline_address,
290         const char *initrd_image, unsigned int initrd_address)
291 {
292         int serialfd;
293         struct termios my_termios;
294         char c;
295         int recognized;
296         struct pollfd fds[2];
297         int flags;
298         
299         /* Open and configure the serial port */
300         serialfd = open(serial_port, O_RDWR|O_NOCTTY);
301         if(serialfd == -1) {
302                 perror("Unable to open serial port");
303                 return;
304         }
305         
306         /* Thanks to Julien Schmitt (GTKTerm) for figuring out the correct parameters
307          * to put into that weird struct.
308          */
309         tcgetattr(serialfd, &my_termios);
310         my_termios.c_cflag = doublerate ? B230400 : B115200;
311         my_termios.c_cflag |= CS8;
312         my_termios.c_cflag |= CREAD;
313         my_termios.c_iflag = IGNPAR | IGNBRK;
314         my_termios.c_cflag |= CLOCAL;
315         my_termios.c_oflag = 0;
316         my_termios.c_lflag = 0;
317         my_termios.c_cc[VTIME] = 0;
318         my_termios.c_cc[VMIN] = 1;
319         tcsetattr(serialfd, TCSANOW, &my_termios);
320         tcflush(serialfd, TCOFLUSH);
321         tcflush(serialfd, TCIFLUSH);
322         
323         /* Prepare the fdset for poll() */
324         fds[0].fd = 0;
325         fds[0].events = POLLIN;
326         fds[1].fd = serialfd;
327         fds[1].events = POLLIN;
328
329         recognized = 0;
330         flags = fcntl(serialfd, F_GETFL, 0);
331         while(1) {
332                 fds[0].revents = 0;
333                 fds[1].revents = 0;
334                 
335                 /* poll() behaves strangely when the serial port descriptor is in
336                  * blocking mode. So work around this.
337                  */
338                 fcntl(serialfd, F_SETFL, flags|O_NONBLOCK);
339                 if(poll(&fds[0], 2, -1) < 0) break;
340                 fcntl(serialfd, F_SETFL, flags);
341                 
342                 if(fds[0].revents & POLLIN) {
343                         read(0, &c, 1);
344                         if(write(serialfd, &c, 1) <= 0) break;
345                 }
346                 
347                 if(fds[1].revents & POLLIN) {
348                         if(read(serialfd, &c, 1) <= 0) break;
349                         write(0, &c, 1);
350                         
351                         if(c == sfl_magic_req[recognized]) {
352                                 recognized++;
353                                 if(recognized == SFL_MAGIC_LEN) {
354                                         /* We've got the magic string ! */
355                                         recognized = 0;
356                                         answer_magic(serialfd,
357                                                 kernel_image, kernel_address,
358                                                 cmdline, cmdline_address,
359                                                 initrd_image, initrd_address);
360                                 }
361                         } else {
362                                 if(c == sfl_magic_req[0]) recognized = 1; else recognized = 0;
363                         }
364                 }
365         }
366         
367         close(serialfd);
368 }
369
370 enum {
371         OPTION_PORT,
372         OPTION_DOUBLERATE,
373         OPTION_KERNEL,
374         OPTION_KERNELADR,
375         OPTION_CMDLINE,
376         OPTION_CMDLINEADR,
377         OPTION_INITRD,
378         OPTION_INITRDADR
379 };
380
381 static const struct option options[] = {
382         {
383                 .name = "port",
384                 .has_arg = 1,
385                 .val = OPTION_PORT
386         },
387         {
388                 .name = "double-rate",
389                 .has_arg = 0,
390                 .val = OPTION_DOUBLERATE
391         },
392         {
393                 .name = "kernel",
394                 .has_arg = 1,
395                 .val = OPTION_KERNEL
396         },
397         {
398                 .name = "kernel-adr",
399                 .has_arg = 1,
400                 .val = OPTION_KERNELADR
401         },
402         {
403                 .name = "cmdline",
404                 .has_arg = 1,
405                 .val = OPTION_CMDLINE
406         },
407         {
408                 .name = "cmdline-adr",
409                 .has_arg = 1,
410                 .val = OPTION_CMDLINEADR
411         },
412         {
413                 .name = "initrd",
414                 .has_arg = 1,
415                 .val = OPTION_INITRD
416         },
417         {
418                 .name = "initrd-adr",
419                 .has_arg = 1,
420                 .val = OPTION_INITRDADR
421         },
422         {
423                 .name = NULL
424         }
425 };
426
427 static void print_usage()
428 {
429         fprintf(stderr, "Serial boot program for the Milkymist VJ SoC - v. 1.1\n");
430         fprintf(stderr, "Copyright (C) 2007, 2008, 2009 Sebastien Bourdeauducq\n\n");
431
432         fprintf(stderr, "This program is free software: you can redistribute it and/or modify\n");
433         fprintf(stderr, "it under the terms of the GNU General Public License as published by\n");
434         fprintf(stderr, "the Free Software Foundation, version 3 of the License.\n\n");
435
436         fprintf(stderr, "Usage: flterm --port <port> [--double-rate]\n");
437         fprintf(stderr, "              --kernel <kernel_image> [--kernel-adr <address>]\n");
438         fprintf(stderr, "              [--cmdline <cmdline> [--cmdline-adr <address>]]\n");
439         fprintf(stderr, "              [--initrd <initrd_image> [--initrd-adr <address>]]\n\n");
440         printf("Default load addresses:\n");
441         fprintf(stderr, "  kernel:  0x%08x\n", DEFAULT_KERNELADR);
442         fprintf(stderr, "  cmdline: 0x%08x\n", DEFAULT_CMDLINEADR);
443         fprintf(stderr, "  initrd:  0x%08x\n", DEFAULT_INITRDADR);
444 }
445
446 int main(int argc, char *argv[])
447 {
448         int opt;
449         char *serial_port;
450         int doublerate;
451         char *kernel_image;
452         unsigned int kernel_address;
453         char *cmdline;
454         unsigned int cmdline_address;
455         char *initrd_image;
456         unsigned int initrd_address;
457         char *endptr;
458         struct termios otty, ntty;
459         
460         /* Fetch command line arguments */
461         serial_port = NULL;
462         doublerate = 0;
463         kernel_image = NULL;
464         kernel_address = DEFAULT_KERNELADR;
465         cmdline = NULL;
466         cmdline_address = DEFAULT_CMDLINEADR;
467         initrd_image = NULL;
468         initrd_address = DEFAULT_INITRDADR;
469         while((opt = getopt_long(argc, argv, "", options, NULL)) != -1) {
470                 if(opt == '?') {
471                         print_usage();
472                         return 1;
473                 }
474                 switch(opt) {
475                         case OPTION_PORT:
476                                 free(serial_port);
477                                 serial_port = strdup(optarg);
478                                 break;
479                         case OPTION_DOUBLERATE:
480                                 doublerate = 1;
481                                 break;
482                         case OPTION_KERNEL:
483                                 free(kernel_image);
484                                 kernel_image = strdup(optarg);
485                                 break;
486                         case OPTION_KERNELADR:
487                                 kernel_address = strtoul(optarg, &endptr, 0);
488                                 if(*endptr != 0) kernel_address = 0;
489                                 break;
490                         case OPTION_CMDLINE:
491                                 free(cmdline);
492                                 cmdline = strdup(optarg);
493                                 break;
494                         case OPTION_CMDLINEADR:
495                                 cmdline_address = strtoul(optarg, &endptr, 0);
496                                 if(*endptr != 0) cmdline_address = 0;
497                                 break;
498                         case OPTION_INITRD:
499                                 free(initrd_image);
500                                 initrd_image = strdup(optarg);
501                                 break;
502                         case OPTION_INITRDADR:
503                                 initrd_address = strtoul(optarg, &endptr, 0);
504                                 if(*endptr != 0) initrd_address = 0;
505                                 break;
506                 }
507         }
508
509         if((serial_port == NULL) || (kernel_image == NULL)) {
510                 print_usage();
511                 return 1;
512         }
513
514         /* Banner */
515         printf("[FLTERM] Starting...\n");
516         
517         /* Set up stdin/out */
518         tcgetattr(0, &otty);
519         ntty = otty;
520         ntty.c_lflag &= ~(ECHO | ICANON);
521         tcsetattr(0, TCSANOW, &ntty);
522         
523         /* Do the bulk of the work */
524         do_terminal(serial_port, doublerate,
525                 kernel_image, kernel_address,
526                 cmdline, cmdline_address,
527                 initrd_image, initrd_address);
528         
529         /* Restore stdin/out into their previous state */
530         tcsetattr(0, TCSANOW, &otty);
531         
532         return 0;
533 }