testpico: show that vres clear is separate
[megadrive.git] / mega-usb-pro / mega-usb-pro.c
1 /*
2  * Tool for USB communication with Mega Everdrive Pro
3  * Copyright (c) 2023 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 <stdint.h>
30 #include <assert.h>
31 #include <errno.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <arpa/inet.h> // hton
36 #include <termios.h>
37 #include <poll.h>
38 #include <unistd.h>
39
40 /* from krikzz/MEGA-PRO/megalink/megalink/Edio.cs */
41 enum megapro_cmd {
42         CMD_STATUS = 0x10,
43         CMD_GET_MODE = 0x11,
44         CMD_IO_RST = 0x12,
45         CMD_GET_VDC = 0x13,
46         CMD_RTC_GET = 0x14,
47         CMD_RTC_SET = 0x15,
48         CMD_FLA_RD = 0x16,
49         CMD_FLA_WR = 0x17,
50         CMD_FLA_WR_SDC = 0x18,
51         CMD_MEM_RD = 0x19,
52         CMD_MEM_WR = 0x1A,
53         CMD_MEM_SET = 0x1B,
54         CMD_MEM_TST = 0x1C,
55         CMD_MEM_CRC = 0x1D,
56         CMD_FPG_USB = 0x1E,
57         CMD_FPG_SDC = 0x1F,
58         CMD_FPG_FLA = 0x20,
59         CMD_FPG_CFG = 0x21,
60         CMD_USB_WR = 0x22,
61         CMD_FIFO_WR = 0x23,
62         CMD_UART_WR = 0x24,
63         CMD_REINIT = 0x25,
64         CMD_SYS_INF = 0x26,
65         CMD_GAME_CTR = 0x27,
66         CMD_UPD_EXEC = 0x28,
67         CMD_HOST_RST = 0x29,
68
69         CMD_DISK_INIT = 0xC0,
70         CMD_DISK_RD = 0xC1,
71         CMD_DISK_WR = 0xC2,
72         CMD_F_DIR_OPN = 0xC3,
73         CMD_F_DIR_RD = 0xC4,
74         CMD_F_DIR_LD = 0xC5,
75         CMD_F_DIR_SIZE = 0xC6,
76         CMD_F_DIR_PATH = 0xC7,
77         CMD_F_DIR_GET = 0xC8,
78         CMD_F_FOPN = 0xC9,
79         CMD_F_FRD = 0xCA,
80         CMD_F_FRD_MEM = 0xCB,
81         CMD_F_FWR = 0xCC,
82         CMD_F_FWR_MEM = 0xCD,
83         CMD_F_FCLOSE = 0xCE,
84         CMD_F_FPTR = 0xCF,
85         CMD_F_FINFO = 0xD0,
86         CMD_F_FCRC = 0xD1,
87         CMD_F_DIR_MK = 0xD2,
88         CMD_F_DEL = 0xD3,
89
90         CMD_USB_RECOV = 0xF0,
91         CMD_RUN_APP = 0xF1,
92 };
93
94 #define MAX_ROM_SIZE 0xF80000
95
96 #define ADDR_ROM    0x0000000
97 #define ADDR_SRAM   0x1000000
98 #define ADDR_BRAM   0x1080000
99 #define ADDR_CFG    0x1800000
100 #define ADDR_SSR    0x1802000
101 #define ADDR_FIFO   0x1810000
102
103 #define SIZE_ROMX   0x1000000
104 #define SIZE_SRAM     0x80000
105 #define SIZE_BRAM     0x80000
106
107 #define HOST_RST_OFF        0
108 #define HOST_RST_SOFT       1
109 #define HOST_RST_HARD       2
110
111 #define fatal(fmt, ...) do { \
112         fprintf(stderr, "\n:%d " fmt, __LINE__, ##__VA_ARGS__); \
113         exit(1); \
114 } while (0)
115
116 static int serial_setup(int fd)
117 {
118         struct termios tty;
119         int ret;
120
121         memset(&tty, 0, sizeof(tty));
122
123         ret = tcgetattr(fd, &tty);
124         if (ret != 0)
125         {
126                 perror("tcgetattr");
127                 return 1;
128         }
129
130         tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
131                         | INLCR | IGNCR | ICRNL | IXON);
132         tty.c_oflag &= ~OPOST;
133         tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
134         tty.c_cflag &= ~(CSIZE | PARENB);
135         tty.c_cflag |= CS8;
136
137         //tty.c_cc[VMIN] = 1;
138         //tty.c_cc[VTIME] = 50;            // seconds*10 read timeout?
139
140         ret = tcsetattr(fd, TCSANOW, &tty);
141         if (ret != 0) {
142                 perror("tcsetattr");
143                 return ret;
144         }
145
146         return 0;
147 }
148
149 static void flush_input(int fd)
150 {
151         struct pollfd pfd = { fd, POLLIN, 0 };
152         uint8_t buf[0x10000];
153         int b = 0, total = 0;
154
155         while (poll(&pfd, 1, 10) > 0) {
156                 b = read(fd, buf, sizeof(buf));
157                 if (b > 0)
158                         total += b;
159         }
160
161         if (total != 0) {
162                 fprintf(stderr, "flushed %d leftover bytes", total);
163                 if (b > 0)
164                         fprintf(stderr, ", last '%c' %02x\n", buf[b-1], buf[b-1]);
165                 puts("");
166         }
167 }
168
169 static void serial_write(int fd, const void *data, size_t size)
170 {
171         int ret;
172
173         ret = write(fd, data, size);
174         if (ret != size) {
175                 fprintf(stderr, "write %d/%zd: ", ret, size);
176                 perror("");
177                 exit(1);
178         }
179 }
180
181 static void serial_read(int fd, void *data, size_t size)
182 {
183         size_t got = 0;
184         int ret;
185
186         while (got < size) {
187                 ret = read(fd, (char *)data + got, size - got);
188                 if (ret <= 0) {
189                         fprintf(stderr, "read %d %zd/%zd: ",
190                                 ret, got, size);
191                         perror("");
192                         exit(1);
193                 }
194                 got += ret;
195         }
196 }
197
198 #define serial_read_expect_byte(fd, b) \
199         serial_read_expect_byte_(fd, b, __LINE__)
200 static void serial_read_expect_byte_(int fd, char expected, int line)
201 {
202         struct pollfd pfd = { fd, POLLIN, 0 };
203         char byte = 0;
204         int ret;
205
206         ret = poll(&pfd, 1, 5000); // 32x reset is > 3s
207         if (ret <= 0)
208                 fatal(":%d poll %d,%d\n", line, ret, errno);
209
210         serial_read(fd, &byte, sizeof(byte));
211         if (byte != expected) {
212                 fatal(":%d wrong response: '%c' %02x, expected '%c'\n",
213                       line, byte, byte, expected);
214         }
215 }
216
217 static void send_cmd_a(int fd, enum megapro_cmd cmd, const void *arg, size_t alen)
218 {
219         uint8_t buf[4] = { '+', '+' ^ 0xff, cmd, cmd ^ 0xff };
220         serial_write(fd, buf, sizeof(buf));
221         if (alen)
222                 serial_write(fd, arg, alen);
223 }
224
225 #if 0
226 static void send_cmd(int fd, enum megapro_cmd cmd)
227 {
228         send_cmd_a(fd, cmd, NULL, 0);
229 }
230 #endif
231
232 static void send_cmd_addr_len(int fd, enum megapro_cmd cmd, uint32_t addr, uint32_t len)
233 {
234         struct {
235                 uint32_t addr;
236                 uint32_t len;
237                 uint8_t exec; // ?
238         } __attribute__((packed)) args;
239         assert(sizeof(args) == 9);
240         args.addr = htonl(addr);
241         args.len = htonl(len);
242         args.exec = 0;
243         send_cmd_a(fd, cmd, &args, sizeof(args));
244 }
245
246 static void send_reset_cmd(int fd, uint8_t type)
247 {
248         static int prev_type = -2;
249
250         if (type == prev_type)
251                 return;
252
253         send_cmd_a(fd, CMD_HOST_RST, &type, sizeof(type));
254
255         //if (type == HOST_RST_OFF && prev_type != HOST_RST_OFF)
256         if (!(type & 1) && (prev_type & 1))
257                 serial_read_expect_byte(fd, 'r');
258         prev_type = type;
259 }
260
261 static void mem_write(int fd, uint32_t addr, const void *buf, size_t buf_size)
262 {
263         if (addr < ADDR_CFG)
264                 send_reset_cmd(fd, HOST_RST_SOFT);
265         send_cmd_addr_len(fd, CMD_MEM_WR, addr, buf_size);
266         serial_write(fd, buf, buf_size);
267 }
268
269 static void mem_read(int fd, uint32_t addr, void *buf, size_t buf_size)
270 {
271         if (addr < ADDR_CFG)
272                 send_reset_cmd(fd, HOST_RST_SOFT);
273         send_cmd_addr_len(fd, CMD_MEM_RD, addr, buf_size);
274         serial_read(fd, buf, buf_size);
275 }
276
277 // supposedly fifo communicates with whatever is running on MD side?
278 // (as there is no response to fifo stuff while commercial game runs)
279 static void fifo_write(int fd, const void *buf, size_t buf_size)
280 {
281         mem_write(fd, ADDR_FIFO, buf, buf_size);
282 }
283
284 static void fifo_write_u32(int fd, uint32_t u32)
285 {
286         u32 = htonl(u32);
287         fifo_write(fd, &u32, sizeof(u32));
288 }
289
290 static void fifo_write_str(int fd, const char *s)
291 {
292         fifo_write(fd, s, strlen(s));
293 }
294
295 static void fifo_write_len_n_str(int fd, const char *s)
296 {
297         size_t len = strlen(s);
298         uint16_t len16 = len;
299         assert(len16 == len);
300         len16 = htons(len16);
301         fifo_write(fd, &len16, sizeof(len16));
302         fifo_write(fd, s, len);
303 }
304
305 #define run_t_check(fd) run_t_check_(fd, __LINE__)
306 static void run_t_check_(int fd, int line)
307 {
308         fifo_write_str(fd, "*t");
309         serial_read_expect_byte_(fd, 'k', line);
310 }
311
312 // try to recover from messed up states
313 static void run_t_check_initial(int fd)
314 {
315         struct pollfd pfd = { fd, POLLIN, 0 };
316         char byte = 0;
317         int ret;
318
319         fifo_write_str(fd, "*t");
320         ret = poll(&pfd, 1, 1000);
321         if (ret == 1) {
322                 serial_read(fd, &byte, sizeof(byte));
323                 if (byte == 'k')
324                         return;
325                 fprintf(stderr, "bad response to '*t': '%c' %02x\n", byte, byte);
326         }
327         else if (ret == 0)
328                 fprintf(stderr, "timeout on initial '*t'\n");
329         else
330                 fatal("poll %d,%d\n", ret, errno);
331
332         /*fprintf(stderr, "attempting reset...\n");
333         send_reset_cmd(fd, HOST_RST_OFF);
334         send_reset_cmd(fd, HOST_RST_SOFT);*/
335 }
336
337 static uint32_t send_from_file(int fd, uint32_t addr, uint32_t size_ovr,
338         const char *fname)
339 {
340         uint8_t *buf = NULL;
341         FILE *f = NULL;
342         size_t size;
343         int ret;
344
345         f = fopen(fname, "rb");
346         if (f == NULL)
347                 fatal("fopen %s: %s\n", fname, strerror(errno));
348
349         fseek(f, 0, SEEK_END);
350         size = ftell(f);
351         fseek(f, 0, SEEK_SET);
352         if (size_ovr) {
353                 if (size_ovr > size)
354                         fatal("size override %u > %zd\n", size_ovr, size);
355                 size = size_ovr;
356         }
357         if (size > MAX_ROM_SIZE)
358                 fatal("size too large: %zd\n", size);
359         buf = malloc(size);
360         if (!buf)
361                 fatal("oom\n");
362         ret = fread(buf, 1, size, f);
363         if (ret != size)
364                 fatal("fread %s: %d/%zd %s\n", fname, ret, size, strerror(errno));
365
366         mem_write(fd, addr, buf, size);
367
368         free(buf);
369         fclose(f);
370         return size;
371 }
372
373 static uint32_t recv_to_file(int fd, uint32_t addr, uint32_t size,
374         const char *fname)
375 {
376         uint8_t *buf = NULL;
377         FILE *f = NULL;
378         int ret;
379
380         if (strcmp(fname, "-") == 0)
381                 f = stdout;
382         else
383                 f = fopen(fname, "wb");
384         if (f == NULL)
385                 fatal("fopen %s: %s", fname, strerror(errno));
386
387         buf = malloc(size);
388         if (!buf)
389                 fatal("oom\n");
390
391         mem_read(fd, addr, buf, size);
392
393         ret = fwrite(buf, 1, size, f);
394         if (ret != size)
395                 fatal("fwrite %s: %d/%d %s\n", fname, ret, size, strerror(errno));
396
397         free(buf);
398         if (f == stdout)
399                 fflush(stdout);
400         else
401                 fclose(f);
402         return size;
403 }
404
405 // reference: megalink/Usbio::loadGame
406 static void send_run_game(int fd, uint32_t size_ovr, const char *fname)
407 {
408         const char *fname_out;
409         char name_out[256];
410         uint32_t size;
411
412         fname_out = strrchr(fname, '/');
413         fname_out = fname_out ? fname_out + 1 : fname;
414         snprintf(name_out, sizeof(name_out), "USB:%s", fname_out);
415
416         size = send_from_file(fd, 0, size_ovr, fname);
417         send_reset_cmd(fd, HOST_RST_OFF);
418
419         run_t_check(fd);
420
421         fifo_write_str(fd, "*g");
422         fifo_write_u32(fd, size);
423         fifo_write_len_n_str(fd, name_out);
424
425         serial_read_expect_byte(fd, 0); // not done by reference
426 }
427
428 static void usage(const char *argv0)
429 {
430         fprintf(stderr, "usage:\n"
431                 "%s [-d <ttydevice>] [opts]\n"
432                 "  -rst [type]             reset\n"
433                 "  -l <file> [len]         load and run\n"
434                 "  -w <file> <addr> [len]  write to cart\n"
435                 "  -r <file> <addr> <len>  read from cart\n"
436                 , argv0);
437         exit(1);
438 }
439
440 static void invarg(int argc, char *argv[], int arg)
441 {
442         if (arg < argc)
443                 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
444         else
445                 fprintf(stderr, "missing required argument %d\n", arg);
446         exit(1);
447 }
448
449 static int goodarg(const char *arg)
450 {
451         return arg && arg[0] && (arg[0] != '-' || arg[1] == 0);
452 }
453
454 static void *getarg(int argc, char *argv[], int arg)
455 {
456         if (!goodarg(argv[arg]))
457                 invarg(argc, argv, arg);
458         return argv[arg];
459 }
460
461 int main(int argc, char *argv[])
462 {
463         const char *portname = "/dev/ttyACM0";
464         const char *fname;
465         int no_exit_t_check = 0;
466         uint8_t byte;
467         int arg = 1;
468         int fd;
469
470         if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
471                 usage(argv[0]);
472
473         if (!strcmp(argv[arg], "-d")) {
474                 arg++;
475                 if (argv[arg] != NULL)
476                         portname = argv[arg];
477                 else
478                         invarg(argc, argv, arg);
479                 arg++;
480         }
481
482         fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
483         if (fd < 0) {
484                 fprintf(stderr, "open %s: ", portname);
485                 perror("");
486                 return 1;
487         }
488
489         serial_setup(fd);
490         flush_input(fd);
491         run_t_check_initial(fd);
492
493         for (; arg < argc; arg++)
494         {
495                 uint32_t addr, size = 0;
496                 if (!strcmp(argv[arg], "-rst")) {
497                         byte = HOST_RST_SOFT;
498                         if (goodarg(argv[arg + 1]))
499                                 byte = strtol(argv[++arg], NULL, 0); 
500                         send_reset_cmd(fd, byte);
501                 }
502                 else if (!strcmp(argv[arg], "-l")) {
503                         fname = getarg(argc, argv, ++arg);
504                         if (goodarg(argv[arg + 1]))
505                                 size = strtol(argv[++arg], NULL, 0); 
506                         send_run_game(fd, size, fname);
507                         no_exit_t_check = 1; // disturbs game startup
508                 }
509                 else if (!strcmp(argv[arg], "-w")) {
510                         fname = getarg(argc, argv, ++arg);
511                         addr = strtol(getarg(argc, argv, ++arg), NULL, 0);
512                         if (goodarg(argv[arg + 1]))
513                                 size = strtol(argv[++arg], NULL, 0); 
514                         send_from_file(fd, addr, size, fname);
515                 }
516                 else if (!strcmp(argv[arg], "-r")) {
517                         fname = getarg(argc, argv, ++arg);
518                         addr = strtol(getarg(argc, argv, ++arg), NULL, 0);
519                         size = strtol(getarg(argc, argv, ++arg), NULL, 0);
520                         recv_to_file(fd, addr, size, fname);
521                 }
522         }
523         send_reset_cmd(fd, HOST_RST_OFF);
524         if (!no_exit_t_check)
525                 run_t_check(fd);
526
527         return 0;
528 }