mega-usb: support BE
[megadrive.git] / mega-usb / mega-usb.c
CommitLineData
0a613c3d 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
9f5a788d 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>
0551910b 34#include <arpa/inet.h> // hton
9f5a788d 35#include <termios.h>
36#include <unistd.h>
37
38static int setup(int fd)
39{
9f5a788d 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
71static 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) {
0551910b 77 fprintf(stderr, "write %d/%zd: ", ret, size);
78 perror("");
9f5a788d 79 exit(1);
80 //return -1;
81 }
82
83 return 0;
84}
85
86static int read_from_cart(int fd, void *data, size_t size)
87{
0551910b 88 size_t got = 0;
9f5a788d 89 int ret;
90
0551910b 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;
9f5a788d 101 }
102
103 return 0;
104}
105
106#define send_cmd(fd, cmd) \
0a613c3d 107 write_to_cart(fd, cmd, strlen(cmd))
9f5a788d 108
109static 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
129static 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) \
0a613c3d 150 write_with_check(fd, cmd, strlen(cmd), 'k')
9f5a788d 151
0a613c3d 152static int send_file(int fd, const char *fname, const char *cmd)
9f5a788d 153{
074fa2e1 154 unsigned char blocks_b;
9f5a788d 155 char buf[0x10000];
0a613c3d 156 int retval = -1;
157 FILE *f = NULL;
158 size_t blocksz;
9f5a788d 159 size_t size;
160 int blocks;
9f5a788d 161 int ret;
9f5a788d 162 int i;
163
0a613c3d 164 f = fopen(fname, "rb");
0551910b 165 if (f == NULL) {
0a613c3d 166 fprintf(stderr, "fopen %s: ", fname);
0551910b 167 perror("");
0a613c3d 168 return -1;
9f5a788d 169 }
170
0a613c3d 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 }
9f5a788d 178
0a613c3d 179 if (cmd[1] == 'f')
180 blocksz = 1024;
181 else
182 blocksz = sizeof(buf);
9f5a788d 183
0a613c3d 184 blocks = (size + blocksz - 1) / blocksz;
074fa2e1 185 blocks_b = blocks;
0a613c3d 186
187 send_cmd(fd, cmd);
074fa2e1 188 ret = write_with_check(fd, &blocks_b, 1, 'k');
0a613c3d 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);
9f5a788d 201 }
0a613c3d 202 if (ret < blocksz)
203 memset(buf + ret, 0, blocksz - ret);
204
205 write_to_cart(fd, buf, blocksz);
206 }
9f5a788d 207
0a613c3d 208 ret = read_check_byte(fd, 'd');
209 if (ret)
210 goto out;
9f5a788d 211
0a613c3d 212 retval = 0;
213out:
214 if (f != NULL)
215 fclose(f);
9f5a788d 216
0a613c3d 217 return retval;
218}
9f5a788d 219
0551910b 220static 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;
265out:
266 if (f != NULL)
267 fclose(f);
268 free(buf);
269
270 return retval;
271}
272
0a613c3d 273static 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"
0551910b 286 "custom OS commands:\n"
287 " *xd <addr> <size> <file> - download memory\n"
0a613c3d 288 , argv0);
289 exit(1);
290}
291
292static 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
301int 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]);
9f5a788d 311
0a613c3d 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 }
9f5a788d 320
0a613c3d 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;
9f5a788d 326 }
327
0a613c3d 328 setup(fd);
9f5a788d 329
0a613c3d 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 }
0551910b 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 }
0a613c3d 367
0551910b 368 /* passthrough */
0a613c3d 369 ret = send_cmd_check_k(fd, argv[arg]);
370 }
371
372 return ret;
9f5a788d 373}