add progress bar
[saturn.git] / datalink.c
1 /*
2  * Tool for Saturn USB DataLink device
3  * Copyright (c) 2014 notaz
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining
6  * a copy of this software and associated documentation files (the
7  * "Software"), to deal in the Software without restriction, including
8  * without limitation the rights to use, copy, modify, merge, publish,
9  * distribute, sublicense, and/or sell copies of the Software, and to
10  * permit persons to whom the Software is furnished to do so, subject to
11  * the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be 
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stdint.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <sys/ioctl.h>
35 #include <unistd.h>
36
37 // ugh.. avoid conflicting winsize/termio from sys/ioctl.h
38 #define winsize winsize_
39 #define termio termio_
40 #include <linux/termios.h>
41
42 #define BAUD_RATE 375000
43 #define MAX_DATA_SIZE 191
44 #define PROGRESS_STEP (4 * 1024)
45
46 static int setup(int fd)
47 {
48         struct termios2 tty;
49         //struct termios tty;
50         int ret;
51
52         memset(&tty, 0, sizeof(tty));
53
54         //ret = tcgetattr(fd, &tty);
55         ret = ioctl(fd, TCGETS2, &tty);
56         if (ret != 0) {
57                 perror("TCGETS2");
58                 return ret;
59         }
60
61         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
62                         | INLCR | IGNCR | ICRNL | IXON);
63         tty.c_oflag &= ~OPOST;
64         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
65         tty.c_cflag &= ~(CSIZE | PARENB);
66         tty.c_cflag |= CS8 | CSTOPB | CREAD;
67         tty.c_cflag &= ~CBAUD;
68         tty.c_cflag |= BOTHER;
69         tty.c_ispeed =
70         tty.c_ospeed = BAUD_RATE;
71
72         tty.c_cc[VMIN] = 1;
73         tty.c_cc[VTIME] = 0;    // read timeout deciseconds
74
75         //ret = tcsetattr(fd, TCSANOW, &tty);
76         ret = ioctl(fd, TCSETS2, &tty);
77         if (ret != 0) {
78                 perror("TCSETS2");
79                 return ret;
80         }
81
82         memset(&tty, 0, sizeof(tty));
83         ret = ioctl(fd, TCGETS2, &tty);
84         if (ret != 0) {
85                 perror("TCGETS2");
86                 return ret;
87         }
88
89         if (tty.c_ispeed != BAUD_RATE || tty.c_ospeed != BAUD_RATE) {
90                 fprintf(stderr, "warning: could not set %d baud, got:\n",
91                         BAUD_RATE);
92                 fprintf(stderr, "c_ispeed: %d\n", tty.c_ispeed);
93                 fprintf(stderr, "c_ospeed: %d\n", tty.c_ospeed);
94                 fprintf(stderr, "c_cflag: 0%o\n", tty.c_cflag);
95         }
96
97         return 0;
98 }
99
100 static void print_progress(int done, int total)
101 {
102         int i, step;
103
104         printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
105         printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); /* 20 */
106         printf("\b\b\b\b\b\b");
107         printf("%06x/%06x |", done, total);
108
109         step = total / 20;
110         for (i = step; i <= total; i += step)
111                 printf("%c", done >= i ? '=' : '-');
112         printf("| %3d%%", done * 100 / total);
113         fflush(stdout);
114
115         if (done == total)
116                 printf("\n");
117 }
118
119 static void dump_pkt(const uint8_t *buf, size_t size, const char *msg)
120 {
121         size_t i;
122
123         printf("%s\n", msg);
124
125         for (i = 0; i < size; ) {
126                 printf("%02zx:", i);
127                 while (i < size) {
128                         printf(" %02x", buf[i]);
129                         i++;
130                         if ((i & 15) == 0)
131                                 break;
132                 }
133                 printf("\n");
134         }
135 }
136
137 static uint8_t sum(const uint8_t *buf, size_t size)
138 {
139         uint8_t ret = 0;
140         size_t i;
141
142         for (i = 0; i < size; i++)
143                 ret += buf[i];
144
145         return ret;
146 }
147
148 static int send_pkt(int fd, uint8_t cmd, uint32_t addr, uint8_t len,
149         const void *data, size_t data_size)
150 {
151         uint8_t pkt[200];
152         int ret;
153
154         pkt[0] = 0x5a;
155         pkt[1] = 9 - 2 + data_size;
156         pkt[2] = cmd;
157         pkt[3] = addr >> 24;
158         pkt[4] = addr >> 16;
159         pkt[5] = addr >> 8;
160         pkt[6] = addr;
161         pkt[7] = len;
162         if (data_size > 0)
163                 memcpy(&pkt[8], data, data_size);
164         pkt[8 + data_size] = sum(&pkt[1], 8 + data_size - 1);
165
166         // dump_pkt(pkt, 9 + data_size, "send:");
167
168         ret = write(fd, pkt, 9 + data_size);
169         if (ret != 9 + data_size) {
170                 fprintf(stderr, "write: %d/%zd, cmd %02x, addr %08x: ",
171                         ret, 9 + data_size, cmd, addr);
172                 perror("");
173                 dump_pkt(pkt, 9 + data_size, "");
174                 return -1;
175         }
176
177         return 0;
178 }
179
180 static int recv_pkt(int fd, uint8_t cmd, uint32_t addr,
181         void *data, size_t data_size)
182 {
183         size_t size = 9 + data_size;
184         size_t got;
185         uint8_t pkt[200];
186         uint8_t csum;
187         int ret;
188
189         for (got = 0; got < size; ) {
190                 ret = read(fd, pkt + got, size);
191                 if (ret <= 0) {
192                         fprintf(stderr, "read: %d, %zd/%zd, "
193                                         "cmd %02x, addr %08x: ",
194                                 ret, got, size, cmd, addr);
195                         perror("");
196                         if (got > 0)
197                                 dump_pkt(pkt, got, "received data:");
198                         return -1;
199                 }
200                 got += ret;
201         }
202
203         csum = sum(&pkt[1], size - 2);
204         if (csum != pkt[size - 1]) {
205                 fprintf(stderr, "read: %zd, cmd %02x, addr %08x: "
206                                 "checksum incorrect, expected %02x\n",
207                         got, cmd, addr, csum);
208                 dump_pkt(pkt, got, "");
209                 return -2;
210         }
211
212         if (pkt[0] != 0xa5 || pkt[1] != size - 2 || pkt[2] != 0xff) {
213                 fprintf(stderr, "read: %zd, cmd %02x, addr %08x: "
214                                 "unexpected response header\n",
215                         got, cmd, addr);
216                 dump_pkt(pkt, got, "");
217                 return -2;
218         }
219
220         if (data_size != 0)
221                 memcpy(data, &pkt[8], data_size);
222
223         // dump_pkt(pkt, got, "recv:");
224
225         return 0;
226 }
227
228 static int do_rx(int fd, uint32_t addr, void *buf, size_t size)
229 {
230         size_t size_total = size;
231         int progress_bytes = 0;
232         char *cbuf = buf;
233         int ret;
234         int len;
235
236         /* must use at least 2 packets */
237         if (size <= MAX_DATA_SIZE)
238                 len = size / 2;
239         else
240                 len = MAX_DATA_SIZE;
241
242         ret = send_pkt(fd, 0x01, addr, len, NULL, 0);
243         if (ret != 0)
244                 return ret;
245
246         ret = recv_pkt(fd, 0x01, addr, cbuf, len);
247         if (ret != 0)
248                 return ret;
249
250         addr += len;
251         cbuf += len;
252         size -= len;
253
254         while (size > 0) {
255                 uint8_t cmd = size > MAX_DATA_SIZE ? 0x11 : 0x21;
256
257                 len = size;
258                 if (len > MAX_DATA_SIZE)
259                         len = MAX_DATA_SIZE;
260
261                 ret = send_pkt(fd, cmd, addr, len, NULL, 0);
262                 if (ret != 0)
263                         return ret;
264
265                 ret = recv_pkt(fd, 0x01, addr, cbuf, len);
266                 if (ret != 0)
267                         return ret;
268
269                 addr += len;
270                 cbuf += len;
271                 size -= len;
272
273                 progress_bytes -= len;
274                 if (progress_bytes <= 0 || size == 0) {
275                         print_progress(size_total - size, size_total);
276                         progress_bytes = PROGRESS_STEP;
277                 }
278         }
279
280         return 0;
281 }
282
283 static int do_tx(int fd, uint32_t addr, void *buf, size_t size, int exe)
284 {
285         int progress_bytes = 0;
286         size_t size_total = size;
287         size_t size_last = size;
288         uint32_t addr_last = addr;
289         char *cbuf = buf;
290         uint8_t cmd = 0x09;
291         int ret;
292         int len;
293
294         if (size > MAX_DATA_SIZE) {
295                 /* skip first packet, we'll send it last */
296                 size_last = MAX_DATA_SIZE;
297                 addr += size_last;
298                 cbuf += size_last;
299                 size -= size_last;
300
301                 while (size > 0) {
302                         len = size;
303                         if (len > MAX_DATA_SIZE)
304                                 len = MAX_DATA_SIZE;
305
306                         ret = send_pkt(fd, cmd, addr, len, cbuf, len);
307                         if (ret != 0)
308                                 return ret;
309
310                         ret = recv_pkt(fd, cmd, addr, NULL, 0);
311                         if (ret != 0)
312                                 return ret;
313
314                         addr += len;
315                         cbuf += len;
316                         size -= len;
317
318                         progress_bytes -= len;
319                         if (progress_bytes <= 0) {
320                                 print_progress(size_total - size - size_last,
321                                         size_total);
322                                 progress_bytes = PROGRESS_STEP;
323                         }
324                 }
325         }
326
327         if (exe)
328                 cmd = 0x19;
329
330         ret = send_pkt(fd, cmd, addr_last, size_last, buf, size_last);
331         if (ret != 0)
332                 return ret;
333
334         ret = recv_pkt(fd, cmd, addr_last, NULL, 0);
335         if (ret != 0)
336                 return ret;
337
338         print_progress(size_total, size_total);
339
340         return 0;
341 }
342
343 static void usage(const char *argv0)
344 {
345         printf("usage:\n"
346                 "%s [dev <device>] command(s)\n"
347                 "commands:\n"
348                 "  rx <addr> <size> <outfile>\n"
349                 "  tx <addr> <size> <infile> [x]\n"
350                 "    <outfile> can be \"/hd/\" for a hexdump\n"
351                 "    x instructs to execute uploaded data\n"
352                 "    for tx, size 0 asks to use filesize\n\n"
353                 "useful regions (addr size):\n"
354                 "  0x00000000 0x080000 - BIOS\n"
355                 "  0x00180000 0x010000 - Backup-RAM\n"
356                 "  0x00200000 0x100000 - Work-RAM-L\n"
357                 "  0x05a00000 0x080000 - 68000-RAM\n"
358                 "  0x05c00000 0x080000 - VDP1-VRAM\n"
359                 "  0x05c80000 0x040000 - VDP1-FB\n"
360                 "  0x05e00000 0x080000 - VDP2-VRAM\n"
361                 "  0x05f00000 0x001000 - VDP2-CRAM\n"
362                 "  0x06000000 0x100000 - Work-RAM-H\n"
363                 , argv0);
364         exit(1);
365 }
366
367 static void invarg(int argc, char *argv[], int arg)
368 {
369         if (arg < argc)
370                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
371         else
372                 fprintf(stderr, "missing required argument %d\n", arg);
373         exit(1);
374 }
375
376 int main(int argc, char *argv[])
377 {
378         const char *portname = "/dev/ttyUSB0";
379         const char *fname;
380         struct stat st;
381         void *buf = NULL;
382         FILE *f = NULL;
383         uint32_t addr;
384         size_t size;
385         char *endp;
386         int arg = 1;
387         int ret;
388         int fd;
389
390         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
391                 usage(argv[0]);
392
393         if (!strcmp(argv[arg], "dev")) {
394                 arg++;
395                 if (argv[arg] != NULL)
396                         portname = argv[arg];
397                 else
398                         invarg(argc, argv, arg);
399                 arg++;
400         }
401
402         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
403         if (fd < 0) {
404                 fprintf(stderr, "open %s: ", portname);
405                 perror("");
406                 return 1;
407         }
408
409         ret = setup(fd);
410         if (ret)
411                 return ret;
412
413         while (arg < argc)
414         {
415                 if (!strcmp(argv[arg], "rx") ||
416                     !strcmp(argv[arg], "tx"))
417                 {
418                         int tx = !strcmp(argv[arg], "tx");
419                         int exe = 0;
420
421                         if (argc - arg < 4)
422                                 invarg(argc, argv, argc);
423
424                         endp = NULL;
425                         addr = strtoul(argv[++arg], &endp, 0);
426                         if (endp == NULL || *endp != 0)
427                                 invarg(argc, argv, arg);
428
429                         endp = NULL;
430                         size = strtoul(argv[++arg], &endp, 0);
431                         if (endp == NULL || *endp != 0)
432                                 invarg(argc, argv, arg);
433
434                         fname = argv[++arg];
435                         if (tx || strcmp(fname, "/hd/") != 0) {
436                                 f = fopen(fname, tx ? "rb" : "wb");
437                                 if (f == NULL) {
438                                         fprintf(stderr, "open %s: \n",
439                                                 fname);
440                                         perror("");
441                                         return 1;
442                                 }
443                         }
444
445                         if (tx && size == 0) {
446                                 ret = fstat(fileno(f), &st);
447                                 if (ret != 0) {
448                                         fprintf(stderr, "fstat %s: \n",
449                                                 fname);
450                                         perror("stat");
451                                         return 1;
452                                 }
453                                 size = st.st_size;
454                         }
455
456                         if (size == 0) {
457                                 fprintf(stderr, "size is 0\n");
458                                 return 1;
459                         }
460
461                         buf = realloc(buf, size);
462                         if (buf == NULL) {
463                                 fprintf(stderr, "OOM\n");
464                                 return 1;
465                         }
466
467                         if (tx) {
468                                 ret = fread(buf, 1, size, f);
469                                 if (ret != size) {
470                                         fprintf(stderr, "read %s: \n",
471                                                 fname);
472                                         perror("");
473                                         return 1;
474                                 }
475
476                                 if (arg + 1 < argc
477                                     && !strcmp(argv[arg + 1], "x"))
478                                 {
479                                         exe = 1;
480                                         arg++;
481                                 }
482
483                                 ret = do_tx(fd, addr, buf, size, exe);
484                                 if (ret != 0)
485                                         return ret;
486                         }
487                         else {
488                                 ret = do_rx(fd, addr, buf, size);
489                                 if (ret != 0)
490                                         return ret;
491
492                                 if (f != NULL) {
493                                         ret = fwrite(buf, 1, size, f);
494                                         if (ret != size) {
495                                                 fprintf(stderr,
496                                                         "write %s: \n",
497                                                         fname);
498                                                 perror("");
499                                                 return 1;
500                                         }
501                                 }
502                                 else {
503                                         dump_pkt(buf, size, "");
504                                 }
505                         }
506
507                         if (f != NULL) {
508                                 fclose(f);
509                                 f = NULL;
510                         }
511                         arg++;
512                 }
513                 else {
514                         invarg(argc, argv, arg);
515                 }
516         }
517
518         return 0;
519 }