mega-usb: allow sending any commands
[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 <termios.h>
35 #include <unistd.h>
36
37 static int setup(int fd)
38 {
39         struct termios tty;
40         int ret;
41
42         memset(&tty, 0, sizeof(tty));
43
44         ret = tcgetattr(fd, &tty);
45         if (ret != 0)
46         {
47                 perror("tcgetattr");
48                 return 1;
49         }
50
51         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
52                         | INLCR | IGNCR | ICRNL | IXON);
53         tty.c_oflag &= ~OPOST;
54         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
55         tty.c_cflag &= ~(CSIZE | PARENB);
56         tty.c_cflag |= CS8;
57
58         //tty.c_cc[VMIN] = 1;
59         //tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout
60
61         ret = tcsetattr(fd, TCSANOW, &tty);
62         if (ret != 0) {
63                 perror("tcsetattr");
64                 return ret;
65         }
66
67         return 0;
68 }
69
70 static int write_to_cart(int fd, const void *data, size_t size)
71 {
72         int ret;
73
74         ret = write(fd, data, size);
75         if (ret != size) {
76                 perror("write");
77                 exit(1);
78                 //return -1;
79         }
80
81         return 0;
82 }
83
84 static int read_from_cart(int fd, void *data, size_t size)
85 {
86         int ret;
87
88         ret = read(fd, data, size);
89         if (ret != (int)size) {
90                 perror("read");
91                 exit(1);
92                 //return -1;
93         }
94
95         return 0;
96 }
97
98 #define send_cmd(fd, cmd) \
99         write_to_cart(fd, cmd, strlen(cmd))
100
101 static int read_check_byte(int fd, char expect)
102 {
103         char r = '0';
104         int ret;
105
106         ret = read_from_cart(fd, &r, 1);
107         if (ret != 0) {
108                 fprintf(stderr, "missing response, need '%c'\n", expect);
109                 return -1;
110         }
111
112         if (r != expect) {
113                 fprintf(stderr, "unexpected response: '%c', need '%c'\n",
114                         r, expect);
115                 return -1;
116         }
117
118         return 0;
119 }
120
121 static int write_with_check(int fd, const void *data, size_t size, char chk)
122 {
123         int ret;
124
125         ret = write_to_cart(fd, data, size);
126         if (ret)
127                 return ret;
128
129         ret = read_check_byte(fd, chk);
130         if (ret != 0) {
131                 if (size < 16)
132                         fprintf(stderr, "data sent: '%16s'\n",
133                                 (const char *)data);
134                 exit(1);
135                 //return -1;
136         }
137
138         return 0;
139 }
140
141 #define send_cmd_check_k(fd, cmd) \
142         write_with_check(fd, cmd, strlen(cmd), 'k')
143
144 static int send_file(int fd, const char *fname, const char *cmd)
145 {
146         char buf[0x10000];
147         int retval = -1;
148         FILE *f = NULL;
149         size_t blocksz;
150         size_t size;
151         int blocks;
152         int ret;
153         int i;
154
155         f = fopen(fname, "rb");
156         if (f == NULL)
157         {
158                 fprintf(stderr, "fopen %s: ", fname);
159                 return -1;
160         }
161
162         fseek(f, 0, SEEK_END);
163         size = ftell(f);
164         fseek(f, 0, SEEK_SET);
165         if (size > 0xf00000) {
166                 fprintf(stderr, "size too large: %zd\n", size);
167                 goto out;
168         }
169
170         if (cmd[1] == 'f')
171                 blocksz = 1024;
172         else
173                 blocksz = sizeof(buf);
174
175         blocks = (size + blocksz - 1) / blocksz;
176
177         send_cmd(fd, cmd);
178         ret = write_with_check(fd, &blocks, 1, 'k');
179         if (ret)
180                 return ret;
181
182         // at this point, 'd' will arrive even if we don't
183         // write any more data, what's the point of that?
184         // probably a timeout?
185
186         for (i = 0; i < blocks; i++) {
187                 ret = fread(buf, 1, blocksz, f);
188                 if (i != blocks - 1 && ret != blocksz) {
189                         fprintf(stderr, "failed to read block %d/%d\n",
190                                 i, blocks);
191                 }
192                 if (ret < blocksz)
193                         memset(buf + ret, 0, blocksz - ret);
194
195                 write_to_cart(fd, buf, blocksz);
196         }
197
198         ret = read_check_byte(fd, 'd');
199         if (ret)
200                 goto out;
201
202         retval = 0;
203 out:
204         if (f != NULL)
205                 fclose(f);
206
207         return retval;
208 }
209
210 static void usage(const char *argv0)
211 {
212         printf("usage:\n"
213                 "%s [-d <ttydevice>] [-f <file>] command(s)\n"
214                 "known commands for ED OS:\n"
215                 "  *g - upload game\n"
216                 "  *w - upload and start OS\n"
217                 "  *o - upload and start OS\n"
218                 "  *f - upload (and start?) FPGA firmware\n"
219                 "  *s - start last ROM (same as pressing start in menu)\n"
220                 "  *r<x> - run SDRAM contents as mapper x:\n"
221                 "    s - sms, m - md, o - OS app, c - cd BIOS, M - md10m\n"
222                 "upload commands must be followed by -f <file>\n"
223                 , argv0);
224         exit(1);
225 }
226
227 static void invarg(int argc, char *argv[], int arg)
228 {
229         if (arg < argc)
230                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
231         else
232                 fprintf(stderr, "missing required argument %d\n", arg);
233         exit(1);
234 }
235
236 int main(int argc, char *argv[])
237 {
238         const char *portname = "/dev/ttyUSB0";
239         const char *pending_cmd = NULL;
240         int ret = -1;
241         int arg = 1;
242         int fd;
243
244         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
245                 usage(argv[0]);
246
247         if (!strcmp(argv[arg], "-d")) {
248                 arg++;
249                 if (argv[arg] != NULL)
250                         portname = argv[arg];
251                 else
252                         invarg(argc, argv, arg);
253                 arg++;
254         }
255
256         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
257         if (fd < 0) {
258                 fprintf(stderr, "open %s: ", portname);
259                 perror("");
260                 return 1;
261         }
262
263         setup(fd);
264
265         send_cmd_check_k(fd, "    *T");
266
267         for (; arg < argc; arg++) {
268                 if (!strcmp(argv[arg], "-f")) {
269                         arg++;
270                         if (argv[arg] == NULL)
271                                 invarg(argc, argv, argc);
272                         if (pending_cmd == NULL)
273                                 // assume game upload
274                                 pending_cmd = "*g";
275
276                         ret = send_file(fd, argv[arg], pending_cmd);
277                         if (ret)
278                                 return ret;
279
280                         pending_cmd = NULL;
281                         continue;
282                 }
283                 if (argv[arg + 1] && !strcmp(argv[arg + 1], "-f")) {
284                         /* we'll avoid sending command if there are
285                          * problems with specified file */
286                         pending_cmd = argv[arg];
287                         continue;
288                 }
289
290                 ret = send_cmd_check_k(fd, argv[arg]);
291         }
292
293         return ret;
294 }