mega-usb: support BE
[megadrive.git] / mega-usb / mega-usb.c
1 /*
2  * Tool for USB communication with Mega Everdrive
3  * Copyright (c) 2013,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 #define _BSD_SOURCE
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <arpa/inet.h> // hton
35 #include <termios.h>
36 #include <unistd.h>
37
38 static int setup(int fd)
39 {
40         struct termios tty;
41         int ret;
42
43         memset(&tty, 0, sizeof(tty));
44
45         ret = tcgetattr(fd, &tty);
46         if (ret != 0)
47         {
48                 perror("tcgetattr");
49                 return 1;
50         }
51
52         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
53                         | INLCR | IGNCR | ICRNL | IXON);
54         tty.c_oflag &= ~OPOST;
55         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
56         tty.c_cflag &= ~(CSIZE | PARENB);
57         tty.c_cflag |= CS8;
58
59         //tty.c_cc[VMIN] = 1;
60         //tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout
61
62         ret = tcsetattr(fd, TCSANOW, &tty);
63         if (ret != 0) {
64                 perror("tcsetattr");
65                 return ret;
66         }
67
68         return 0;
69 }
70
71 static int write_to_cart(int fd, const void *data, size_t size)
72 {
73         int ret;
74
75         ret = write(fd, data, size);
76         if (ret != size) {
77                 fprintf(stderr, "write %d/%zd: ", ret, size);
78                 perror("");
79                 exit(1);
80                 //return -1;
81         }
82
83         return 0;
84 }
85
86 static int read_from_cart(int fd, void *data, size_t size)
87 {
88         size_t got = 0;
89         int ret;
90
91         while (got < size) {
92                 ret = read(fd, (char *)data + got, size - got);
93                 if (ret <= 0) {
94                         fprintf(stderr, "read %d %zd/%zd: ",
95                                 ret, got, size);
96                         perror("");
97                         exit(1);
98                         //return -1;
99                 }
100                 got += ret;
101         }
102
103         return 0;
104 }
105
106 #define send_cmd(fd, cmd) \
107         write_to_cart(fd, cmd, strlen(cmd))
108
109 static int read_check_byte(int fd, char expect)
110 {
111         char r = '0';
112         int ret;
113
114         ret = read_from_cart(fd, &r, 1);
115         if (ret != 0) {
116                 fprintf(stderr, "missing response, need '%c'\n", expect);
117                 return -1;
118         }
119
120         if (r != expect) {
121                 fprintf(stderr, "unexpected response: '%c', need '%c'\n",
122                         r, expect);
123                 return -1;
124         }
125
126         return 0;
127 }
128
129 static int write_with_check(int fd, const void *data, size_t size, char chk)
130 {
131         int ret;
132
133         ret = write_to_cart(fd, data, size);
134         if (ret)
135                 return ret;
136
137         ret = read_check_byte(fd, chk);
138         if (ret != 0) {
139                 if (size < 16)
140                         fprintf(stderr, "data sent: '%16s'\n",
141                                 (const char *)data);
142                 exit(1);
143                 //return -1;
144         }
145
146         return 0;
147 }
148
149 #define send_cmd_check_k(fd, cmd) \
150         write_with_check(fd, cmd, strlen(cmd), 'k')
151
152 static int send_file(int fd, const char *fname, const char *cmd)
153 {
154         unsigned char blocks_b;
155         char buf[0x10000];
156         int retval = -1;
157         FILE *f = NULL;
158         size_t blocksz;
159         size_t size;
160         int blocks;
161         int ret;
162         int i;
163
164         f = fopen(fname, "rb");
165         if (f == NULL) {
166                 fprintf(stderr, "fopen %s: ", fname);
167                 perror("");
168                 return -1;
169         }
170
171         fseek(f, 0, SEEK_END);
172         size = ftell(f);
173         fseek(f, 0, SEEK_SET);
174         if (size > 0xf00000) {
175                 fprintf(stderr, "size too large: %zd\n", size);
176                 goto out;
177         }
178
179         if (cmd[1] == 'f')
180                 blocksz = 1024;
181         else
182                 blocksz = sizeof(buf);
183
184         blocks = (size + blocksz - 1) / blocksz;
185         blocks_b = blocks;
186
187         send_cmd(fd, cmd);
188         ret = write_with_check(fd, &blocks_b, 1, 'k');
189         if (ret)
190                 return ret;
191
192         // at this point, 'd' will arrive even if we don't
193         // write any more data, what's the point of that?
194         // probably a timeout?
195
196         for (i = 0; i < blocks; i++) {
197                 ret = fread(buf, 1, blocksz, f);
198                 if (i != blocks - 1 && ret != blocksz) {
199                         fprintf(stderr, "failed to read block %d/%d\n",
200                                 i, blocks);
201                 }
202                 if (ret < blocksz)
203                         memset(buf + ret, 0, blocksz - ret);
204
205                 write_to_cart(fd, buf, blocksz);
206         }
207
208         ret = read_check_byte(fd, 'd');
209         if (ret)
210                 goto out;
211
212         retval = 0;
213 out:
214         if (f != NULL)
215                 fclose(f);
216
217         return retval;
218 }
219
220 static int recv_file(int fd, const char *fname, unsigned int addr,
221         unsigned int size)
222 {
223         int retval = -1;
224         char *buf = NULL;
225         FILE *f = NULL;
226         struct {
227                 unsigned int addr;
228                 unsigned int size;
229         } addr_size;
230         int ret;
231
232         f = fopen(fname, "wb");
233         if (f == NULL) {
234                 fprintf(stderr, "fopen %s: ", fname);
235                 perror("");
236                 return -1;
237         }
238
239         buf = malloc(size);
240         if (buf == NULL) {
241                 fprintf(stderr, "OOM?\n");
242                 goto out;
243         }
244
245         send_cmd(fd, "*xd");
246
247         addr_size.addr = htonl(addr);
248         addr_size.size = htonl(size);
249         ret = write_with_check(fd, &addr_size, sizeof(addr_size), 'k');
250         if (ret)
251                 return ret;
252
253         ret = read_from_cart(fd, buf, size);
254         if (ret)
255                 goto out;
256
257         ret = fwrite(buf, 1, size, f);
258         if (ret != size) {
259                 fprintf(stderr, "fwrite %d/%d: ", ret, size);
260                 perror("");
261                 goto out;
262         }
263
264         retval = 0;
265 out:
266         if (f != NULL)
267                 fclose(f);
268         free(buf);
269
270         return retval;
271 }
272
273 static void usage(const char *argv0)
274 {
275         printf("usage:\n"
276                 "%s [-d <ttydevice>] [-f <file>] command(s)\n"
277                 "known commands for ED OS:\n"
278                 "  *g - upload game\n"
279                 "  *w - upload and start OS\n"
280                 "  *o - upload and start OS\n"
281                 "  *f - upload (and start?) FPGA firmware\n"
282                 "  *s - start last ROM (same as pressing start in menu)\n"
283                 "  *r<x> - run SDRAM contents as mapper x:\n"
284                 "    s - sms, m - md, o - OS app, c - cd BIOS, M - md10m\n"
285                 "upload commands must be followed by -f <file>\n"
286                 "custom OS commands:\n"
287                 "  *xd <addr> <size> <file> - download memory\n"
288                 , argv0);
289         exit(1);
290 }
291
292 static void invarg(int argc, char *argv[], int arg)
293 {
294         if (arg < argc)
295                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
296         else
297                 fprintf(stderr, "missing required argument %d\n", arg);
298         exit(1);
299 }
300
301 int main(int argc, char *argv[])
302 {
303         const char *portname = "/dev/ttyUSB0";
304         const char *pending_cmd = NULL;
305         int ret = -1;
306         int arg = 1;
307         int fd;
308
309         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
310                 usage(argv[0]);
311
312         if (!strcmp(argv[arg], "-d")) {
313                 arg++;
314                 if (argv[arg] != NULL)
315                         portname = argv[arg];
316                 else
317                         invarg(argc, argv, arg);
318                 arg++;
319         }
320
321         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
322         if (fd < 0) {
323                 fprintf(stderr, "open %s: ", portname);
324                 perror("");
325                 return 1;
326         }
327
328         setup(fd);
329
330         send_cmd_check_k(fd, "    *T");
331
332         for (; arg < argc; arg++) {
333                 if (!strcmp(argv[arg], "-f")) {
334                         arg++;
335                         if (argv[arg] == NULL)
336                                 invarg(argc, argv, argc);
337                         if (pending_cmd == NULL)
338                                 // assume game upload
339                                 pending_cmd = "*g";
340
341                         ret = send_file(fd, argv[arg], pending_cmd);
342                         if (ret)
343                                 return ret;
344
345                         pending_cmd = NULL;
346                         continue;
347                 }
348                 if (argv[arg + 1] && !strcmp(argv[arg + 1], "-f")) {
349                         /* we'll avoid sending command if there are
350                          * problems with specified file */
351                         pending_cmd = argv[arg];
352                         continue;
353                 }
354                 /* custom OS commands */
355                 if (!strcmp(argv[arg], "*xd")) {
356                         unsigned int addr, size;
357                         if (arg + 3 >= argc)
358                                 invarg(argc, argv, argc);
359                         addr = strtoul(argv[++arg], NULL, 0);
360                         size = strtoul(argv[++arg], NULL, 0);
361                         ret = recv_file(fd, argv[++arg], addr, size);
362                         if (ret)
363                                 return ret;
364
365                         continue;
366                 }
367
368                 /* passthrough */
369                 ret = send_cmd_check_k(fd, argv[arg]);
370         }
371
372         return ret;
373 }