add progress bar
[saturn.git] / datalink.c
CommitLineData
01698af3 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
d6a44ec7 44#define PROGRESS_STEP (4 * 1024)
01698af3 45
46static 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
d6a44ec7 100static 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
01698af3 119static 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
137static 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
148static 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
180static 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
228static int do_rx(int fd, uint32_t addr, void *buf, size_t size)
229{
d6a44ec7 230 size_t size_total = size;
231 int progress_bytes = 0;
01698af3 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;
d6a44ec7 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 }
01698af3 278 }
279
280 return 0;
281}
282
283static int do_tx(int fd, uint32_t addr, void *buf, size_t size, int exe)
284{
d6a44ec7 285 int progress_bytes = 0;
286 size_t size_total = size;
01698af3 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;
d6a44ec7 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 }
01698af3 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
d6a44ec7 338 print_progress(size_total, size_total);
339
01698af3 340 return 0;
341}
342
343static 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"
d6a44ec7 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);
01698af3 364 exit(1);
365}
366
367static 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
376int 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}