initial commit
[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
45 static int setup(int fd)
46 {
47         struct termios2 tty;
48         //struct termios tty;
49         int ret;
50
51         memset(&tty, 0, sizeof(tty));
52
53         //ret = tcgetattr(fd, &tty);
54         ret = ioctl(fd, TCGETS2, &tty);
55         if (ret != 0) {
56                 perror("TCGETS2");
57                 return ret;
58         }
59
60         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
61                         | INLCR | IGNCR | ICRNL | IXON);
62         tty.c_oflag &= ~OPOST;
63         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
64         tty.c_cflag &= ~(CSIZE | PARENB);
65         tty.c_cflag |= CS8 | CSTOPB | CREAD;
66         tty.c_cflag &= ~CBAUD;
67         tty.c_cflag |= BOTHER;
68         tty.c_ispeed =
69         tty.c_ospeed = BAUD_RATE;
70
71         tty.c_cc[VMIN] = 1;
72         tty.c_cc[VTIME] = 0;    // read timeout deciseconds
73
74         //ret = tcsetattr(fd, TCSANOW, &tty);
75         ret = ioctl(fd, TCSETS2, &tty);
76         if (ret != 0) {
77                 perror("TCSETS2");
78                 return ret;
79         }
80
81         memset(&tty, 0, sizeof(tty));
82         ret = ioctl(fd, TCGETS2, &tty);
83         if (ret != 0) {
84                 perror("TCGETS2");
85                 return ret;
86         }
87
88         if (tty.c_ispeed != BAUD_RATE || tty.c_ospeed != BAUD_RATE) {
89                 fprintf(stderr, "warning: could not set %d baud, got:\n",
90                         BAUD_RATE);
91                 fprintf(stderr, "c_ispeed: %d\n", tty.c_ispeed);
92                 fprintf(stderr, "c_ospeed: %d\n", tty.c_ospeed);
93                 fprintf(stderr, "c_cflag: 0%o\n", tty.c_cflag);
94         }
95
96         return 0;
97 }
98
99 static void dump_pkt(const uint8_t *buf, size_t size, const char *msg)
100 {
101         size_t i;
102
103         printf("%s\n", msg);
104
105         for (i = 0; i < size; ) {
106                 printf("%02zx:", i);
107                 while (i < size) {
108                         printf(" %02x", buf[i]);
109                         i++;
110                         if ((i & 15) == 0)
111                                 break;
112                 }
113                 printf("\n");
114         }
115 }
116
117 static uint8_t sum(const uint8_t *buf, size_t size)
118 {
119         uint8_t ret = 0;
120         size_t i;
121
122         for (i = 0; i < size; i++)
123                 ret += buf[i];
124
125         return ret;
126 }
127
128 static int send_pkt(int fd, uint8_t cmd, uint32_t addr, uint8_t len,
129         const void *data, size_t data_size)
130 {
131         uint8_t pkt[200];
132         int ret;
133
134         pkt[0] = 0x5a;
135         pkt[1] = 9 - 2 + data_size;
136         pkt[2] = cmd;
137         pkt[3] = addr >> 24;
138         pkt[4] = addr >> 16;
139         pkt[5] = addr >> 8;
140         pkt[6] = addr;
141         pkt[7] = len;
142         if (data_size > 0)
143                 memcpy(&pkt[8], data, data_size);
144         pkt[8 + data_size] = sum(&pkt[1], 8 + data_size - 1);
145
146         // dump_pkt(pkt, 9 + data_size, "send:");
147
148         ret = write(fd, pkt, 9 + data_size);
149         if (ret != 9 + data_size) {
150                 fprintf(stderr, "write: %d/%zd, cmd %02x, addr %08x: ",
151                         ret, 9 + data_size, cmd, addr);
152                 perror("");
153                 dump_pkt(pkt, 9 + data_size, "");
154                 return -1;
155         }
156
157         return 0;
158 }
159
160 static int recv_pkt(int fd, uint8_t cmd, uint32_t addr,
161         void *data, size_t data_size)
162 {
163         size_t size = 9 + data_size;
164         size_t got;
165         uint8_t pkt[200];
166         uint8_t csum;
167         int ret;
168
169         for (got = 0; got < size; ) {
170                 ret = read(fd, pkt + got, size);
171                 if (ret <= 0) {
172                         fprintf(stderr, "read: %d, %zd/%zd, "
173                                         "cmd %02x, addr %08x: ",
174                                 ret, got, size, cmd, addr);
175                         perror("");
176                         if (got > 0)
177                                 dump_pkt(pkt, got, "received data:");
178                         return -1;
179                 }
180                 got += ret;
181         }
182
183         csum = sum(&pkt[1], size - 2);
184         if (csum != pkt[size - 1]) {
185                 fprintf(stderr, "read: %zd, cmd %02x, addr %08x: "
186                                 "checksum incorrect, expected %02x\n",
187                         got, cmd, addr, csum);
188                 dump_pkt(pkt, got, "");
189                 return -2;
190         }
191
192         if (pkt[0] != 0xa5 || pkt[1] != size - 2 || pkt[2] != 0xff) {
193                 fprintf(stderr, "read: %zd, cmd %02x, addr %08x: "
194                                 "unexpected response header\n",
195                         got, cmd, addr);
196                 dump_pkt(pkt, got, "");
197                 return -2;
198         }
199
200         if (data_size != 0)
201                 memcpy(data, &pkt[8], data_size);
202
203         // dump_pkt(pkt, got, "recv:");
204
205         return 0;
206 }
207
208 static int do_rx(int fd, uint32_t addr, void *buf, size_t size)
209 {
210         char *cbuf = buf;
211         int ret;
212         int len;
213
214         /* must use at least 2 packets */
215         if (size <= MAX_DATA_SIZE)
216                 len = size / 2;
217         else
218                 len = MAX_DATA_SIZE;
219
220         ret = send_pkt(fd, 0x01, addr, len, NULL, 0);
221         if (ret != 0)
222                 return ret;
223
224         ret = recv_pkt(fd, 0x01, addr, cbuf, len);
225         if (ret != 0)
226                 return ret;
227
228         addr += len;
229         cbuf += len;
230         size -= len;
231
232         while (size > 0) {
233                 uint8_t cmd = size > MAX_DATA_SIZE ? 0x11 : 0x21;
234
235                 len = size;
236                 if (len > MAX_DATA_SIZE)
237                         len = MAX_DATA_SIZE;
238
239                 ret = send_pkt(fd, cmd, addr, len, NULL, 0);
240                 if (ret != 0)
241                         return ret;
242
243                 ret = recv_pkt(fd, 0x01, addr, cbuf, len);
244                 if (ret != 0)
245                         return ret;
246
247                 addr += len;
248                 cbuf += len;
249                 size -= len;
250         }
251
252         return 0;
253 }
254
255 static int do_tx(int fd, uint32_t addr, void *buf, size_t size, int exe)
256 {
257         size_t size_last = size;
258         uint32_t addr_last = addr;
259         char *cbuf = buf;
260         uint8_t cmd = 0x09;
261         int ret;
262         int len;
263
264         if (size > MAX_DATA_SIZE) {
265                 /* skip first packet, we'll send it last */
266                 size_last = MAX_DATA_SIZE;
267                 addr += size_last;
268                 cbuf += size_last;
269                 size -= size_last;
270
271                 while (size > 0) {
272                         len = size;
273                         if (len > MAX_DATA_SIZE)
274                                 len = MAX_DATA_SIZE;
275
276                         ret = send_pkt(fd, cmd, addr, len, cbuf, len);
277                         if (ret != 0)
278                                 return ret;
279
280                         ret = recv_pkt(fd, cmd, addr, NULL, 0);
281                         if (ret != 0)
282                                 return ret;
283
284                         addr += len;
285                         cbuf += len;
286                         size -= len;
287                 }
288         }
289
290         if (exe)
291                 cmd = 0x19;
292
293         ret = send_pkt(fd, cmd, addr_last, size_last, buf, size_last);
294         if (ret != 0)
295                 return ret;
296
297         ret = recv_pkt(fd, cmd, addr_last, NULL, 0);
298         if (ret != 0)
299                 return ret;
300
301         return 0;
302 }
303
304 static void usage(const char *argv0)
305 {
306         printf("usage:\n"
307                 "%s [dev <device>] command(s)\n"
308                 "commands:\n"
309                 "  rx <addr> <size> <outfile>\n"
310                 "  tx <addr> <size> <infile> [x]\n"
311                 "    <outfile> can be \"/hd/\" for a hexdump\n"
312                 "    x instructs to execute uploaded data\n"
313                 "    for tx, size 0 asks to use filesize\n", argv0);
314         exit(1);
315 }
316
317 static void invarg(int argc, char *argv[], int arg)
318 {
319         if (arg < argc)
320                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
321         else
322                 fprintf(stderr, "missing required argument %d\n", arg);
323         exit(1);
324 }
325
326 int main(int argc, char *argv[])
327 {
328         const char *portname = "/dev/ttyUSB0";
329         const char *fname;
330         struct stat st;
331         void *buf = NULL;
332         FILE *f = NULL;
333         uint32_t addr;
334         size_t size;
335         char *endp;
336         int arg = 1;
337         int ret;
338         int fd;
339
340         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
341                 usage(argv[0]);
342
343         if (!strcmp(argv[arg], "dev")) {
344                 arg++;
345                 if (argv[arg] != NULL)
346                         portname = argv[arg];
347                 else
348                         invarg(argc, argv, arg);
349                 arg++;
350         }
351
352         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
353         if (fd < 0) {
354                 fprintf(stderr, "open %s: ", portname);
355                 perror("");
356                 return 1;
357         }
358
359         ret = setup(fd);
360         if (ret)
361                 return ret;
362
363         while (arg < argc)
364         {
365                 if (!strcmp(argv[arg], "rx") ||
366                     !strcmp(argv[arg], "tx"))
367                 {
368                         int tx = !strcmp(argv[arg], "tx");
369                         int exe = 0;
370
371                         if (argc - arg < 4)
372                                 invarg(argc, argv, argc);
373
374                         endp = NULL;
375                         addr = strtoul(argv[++arg], &endp, 0);
376                         if (endp == NULL || *endp != 0)
377                                 invarg(argc, argv, arg);
378
379                         endp = NULL;
380                         size = strtoul(argv[++arg], &endp, 0);
381                         if (endp == NULL || *endp != 0)
382                                 invarg(argc, argv, arg);
383
384                         fname = argv[++arg];
385                         if (tx || strcmp(fname, "/hd/") != 0) {
386                                 f = fopen(fname, tx ? "rb" : "wb");
387                                 if (f == NULL) {
388                                         fprintf(stderr, "open %s: \n",
389                                                 fname);
390                                         perror("");
391                                         return 1;
392                                 }
393                         }
394
395                         if (tx && size == 0) {
396                                 ret = fstat(fileno(f), &st);
397                                 if (ret != 0) {
398                                         fprintf(stderr, "fstat %s: \n",
399                                                 fname);
400                                         perror("stat");
401                                         return 1;
402                                 }
403                                 size = st.st_size;
404                         }
405
406                         if (size == 0) {
407                                 fprintf(stderr, "size is 0\n");
408                                 return 1;
409                         }
410
411                         buf = realloc(buf, size);
412                         if (buf == NULL) {
413                                 fprintf(stderr, "OOM\n");
414                                 return 1;
415                         }
416
417                         if (tx) {
418                                 ret = fread(buf, 1, size, f);
419                                 if (ret != size) {
420                                         fprintf(stderr, "read %s: \n",
421                                                 fname);
422                                         perror("");
423                                         return 1;
424                                 }
425
426                                 if (arg + 1 < argc
427                                     && !strcmp(argv[arg + 1], "x"))
428                                 {
429                                         exe = 1;
430                                         arg++;
431                                 }
432
433                                 ret = do_tx(fd, addr, buf, size, exe);
434                                 if (ret != 0)
435                                         return ret;
436                         }
437                         else {
438                                 ret = do_rx(fd, addr, buf, size);
439                                 if (ret != 0)
440                                         return ret;
441
442                                 if (f != NULL) {
443                                         ret = fwrite(buf, 1, size, f);
444                                         if (ret != size) {
445                                                 fprintf(stderr,
446                                                         "write %s: \n",
447                                                         fname);
448                                                 perror("");
449                                                 return 1;
450                                         }
451                                 }
452                                 else {
453                                         dump_pkt(buf, size, "");
454                                 }
455                         }
456
457                         if (f != NULL) {
458                                 fclose(f);
459                                 f = NULL;
460                         }
461                         arg++;
462                 }
463                 else {
464                         invarg(argc, argv, arg);
465                 }
466         }
467
468         return 0;
469 }