hexed: support PicoDrive savestate loading (only VDP+VRAM for now)
[megadrive.git] / hexed / pc_transfer.c
CommitLineData
f28eaaad 1/*
2 * Copyright (c) 2011, GraÅžvydas Ignotas
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the organization nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
76ba9df5 27#include <stdio.h>
28#include <stdlib.h>
8689c962 29#include <string.h>
76ba9df5 30#include <unistd.h>
31#include <sys/io.h>
32#include <signal.h>
8689c962 33#include <sys/time.h>
4e6ba16d 34#include <zlib.h>
8689c962 35
36#include "transfer.h"
76ba9df5 37
38/*
39 * PC:
40 * BASE+0 ~ data
41 * BASE+1 ~ status: BAOSEI??
42 * /BUSY, ACK, PE, SLCT, ERROR, IRQ
43 * BASE+2 ~ control: ??MISIAS
44 * bidirrectMODE, IRQ_EN, /SLCT_IN, INIT, /AUTO_FD_XT, /STROBE
45 *
46 * SEGA
47 * ?HRL3210
48 * TH, TR, TL, D3, D2, D1, D0
49 *
50 * SEGA PC
51 * 1 D0 <-> 2 D0
52 * 2 D1 <-> 3 D1
53 * 3 D2 <-> 4 D2
54 * 4 D3 <-> 5 D3
55 * 5 +5V
56 * 6 TL <-- 14 /AUTO_FD_XT
57 * 7 TH --> 10 ACK
58 * 8 GND --- 21 GND
59 * 9 TR <-- 17 /SLCT_IN
8689c962 60 *
61 * start: TH low/high, TL high
62 *
63 * TH low - lower nibble: MD ready to recv | MD sent to PC
64 * TL low - lower niblle: sent to MD | ready to recv from MD
65 * TH high - upper nibble: MD ready to recv | MD sent to PC
66 * TL high - upper nibble: sent | ready to recv from MD
76ba9df5 67 */
68
8689c962 69#define ACK_TIMEOUT 2000000
70
71#define PORT_DATA 888
72#define PORT_STATUS 889
73#define PORT_CONTROL 890
74
75#define timediff(now, start) \
76 ((now.tv_sec - start.tv_sec) * 1000000 + now.tv_usec - start.tv_usec)
77
93c5aa8a 78#define PBE2(p) ((*(p) << 8) | *(p+1))
79#define PBE3(p) ((*(p) << 16) | (*(p + 1) << 8) | *(p + 2))
80#define PBE4(p) ((*(p) << 24) | (*(p + 1) << 16) | (*(p + 2) << 8) | *(p + 3))
81
82static void do_exit(const char *msg, const char *where)
76ba9df5 83{
84 /* switch TL back to high */
8689c962 85 outb(0xe0, PORT_CONTROL);
86
93c5aa8a 87 if (where)
88 fprintf(stderr, "%s: ", where);
8689c962 89 if (msg)
93c5aa8a 90 fprintf(stderr, "%s", msg);
76ba9df5 91 exit(1);
92}
93
8689c962 94static void inthandler(int u)
76ba9df5 95{
93c5aa8a 96 do_exit("\n", NULL);
8689c962 97}
76ba9df5 98
93c5aa8a 99static void wait_th_low(const char *where)
8689c962 100{
101 struct timeval start, now;
102
103 gettimeofday(&start, NULL);
104
105 while (inb(PORT_STATUS) & 0x40) {
106 gettimeofday(&now, NULL);
107 if (timediff(now, start) > ACK_TIMEOUT)
93c5aa8a 108 do_exit("timeout waiting TH low\n", where);
76ba9df5 109 }
8689c962 110}
76ba9df5 111
93c5aa8a 112static void wait_th_high(const char *where)
8689c962 113{
114 struct timeval start, now;
115
116 gettimeofday(&start, NULL);
117
118 while (!(inb(PORT_STATUS) & 0x40)) {
119 gettimeofday(&now, NULL);
120 if (timediff(now, start) > ACK_TIMEOUT)
93c5aa8a 121 do_exit("timeout waiting TH high\n", where);
76ba9df5 122 }
8689c962 123}
124
93c5aa8a 125static void output_to_input(void)
126{
127 /* TL high, recv mode; also give time
128 * MD to see TL before we lower it in recv_byte */
129 outb(0xe0, PORT_CONTROL);
130 usleep(4*10); /* must be at least 12+8+8 M68k cycles, 28/7.67M */
131}
132
133static void input_to_output(void)
134{
135 wait_th_low("input_to_output");
136 outb(0xc0, PORT_CONTROL); /* TL high, out mode */
137}
138
8689c962 139static unsigned int recv_byte(void)
140{
141 unsigned int byte;
142
143 outb(0xe2, PORT_CONTROL); /* TL low */
144
93c5aa8a 145 wait_th_low("recv_byte");
8689c962 146
147 byte = inb(PORT_DATA) & 0x0f;
148
149 outb(0xe0, PORT_CONTROL); /* TL high */
150
93c5aa8a 151 wait_th_high("recv_byte");
8689c962 152
153 byte |= inb(PORT_DATA) << 4;
154
155 return byte;
156}
157
93c5aa8a 158static void recv_bytes(unsigned char *b, size_t count)
159{
160 while (count-- > 0)
161 *b++ = recv_byte();
162}
163
8689c962 164static void send_byte(unsigned int byte)
165{
93c5aa8a 166 wait_th_low("recv_bytes");
8689c962 167
168 outb(byte & 0x0f, PORT_DATA);
169 outb(0xc2, PORT_CONTROL); /* TL low */
170
93c5aa8a 171 wait_th_high("recv_bytes");
8689c962 172
173 outb((byte >> 4) & 0x0f, PORT_DATA);
174 outb(0xc0, PORT_CONTROL); /* TL high */
175}
176
93c5aa8a 177static void send_bytes(unsigned char *b, size_t count)
178{
179 while (count-- > 0)
180 send_byte(*b++);
181}
182
8689c962 183static void send_cmd(unsigned int cmd)
184{
185 send_byte(CMD_PREFIX);
186 send_byte(cmd);
187}
188
189static void usage(const char *argv0)
190{
191 fprintf(stderr, "usage:\n%s <cmd> [args]\n"
192 "\tsend <file> <addr> [size]\n"
272bd2ec 193 "\trecv <file> <addr> <size>\n"
4e6ba16d 194 "\tjump <addr>\n"
195 "\tio {r{8,16,32} <addr>,w{8,16,32} <addr> <data>}*\n"
196 "\tloadstate <picodrive_savestate>\n", argv0);
8689c962 197 exit(1);
198}
199
200static unsigned int atoi_or_die(const char *a)
201{
202 char *p = NULL;
203 unsigned int i;
76ba9df5 204
8689c962 205 i = strtoul(a, &p, 0);
76ba9df5 206 if (p == NULL || *p != 0) {
8689c962 207 fprintf(stderr, "atoi: can't convert: %s\n", a);
208 exit(1);
76ba9df5 209 }
210
8689c962 211 return i;
212}
213
4e6ba16d 214static void checked_gzread(gzFile f, void *data, size_t size)
215{
216 unsigned int ret;
217 ret = gzread(f, data, size);
218 if (ret != size) {
219 fprintf(stderr, "gzread returned %d/%zu\n", ret, size);
220 exit(1);
221 }
222}
223
8689c962 224int main(int argc, char *argv[])
225{
93c5aa8a 226 unsigned int addr = 0, size = 0;
227 unsigned int count = 0, i = 0;
8689c962 228 int ret;
229 unsigned char *data;
230 FILE *file = NULL;
231
232 if (argc < 2)
233 usage(argv[0]);
234
235 data = malloc(0x1000000);
76ba9df5 236 if (data == NULL) {
8689c962 237 fprintf(stderr, "can't alloc %d bytes\n", 0x1000000);
76ba9df5 238 return 1;
239 }
240
8689c962 241 /* parse args, read files.. */
242 if (strcmp(argv[1], "send") == 0)
243 {
244 if (argc != 4 && argc != 5)
245 usage(argv[0]);
246
247 file = fopen(argv[2], "rb");
248 if (file == NULL) {
249 fprintf(stderr, "can't open file: %s\n", argv[2]);
250 return 1;
251 }
252
253 addr = atoi_or_die(argv[3]);
254 if (argv[4] == NULL) {
255 fseek(file, 0, SEEK_END);
256 size = ftell(file);
257 fseek(file, 0, SEEK_SET);
258 }
259 else
260 size = atoi_or_die(argv[4]);
261
262 ret = fread(data, 1, size, file);
263 if (ret != size) {
264 fprintf(stderr, "fread returned %d/%d\n", ret, size);
265 perror(NULL);
266 return 1;
267 }
268 }
269 else if (strcmp(argv[1], "recv") == 0)
270 {
271 if (argc != 5)
272 usage(argv[0]);
273
274 file = fopen(argv[2], "wb");
275 if (file == NULL) {
276 fprintf(stderr, "can't open file: %s\n", argv[2]);
277 return 1;
278 }
279
280 addr = atoi_or_die(argv[3]);
281 size = atoi_or_die(argv[4]);
282
283 memset(data, 0, size);
284 }
272bd2ec 285 else if (strcmp(argv[1], "jump") == 0)
286 {
287 if (argc != 3)
288 usage(argv[0]);
289
290 addr = atoi_or_die(argv[2]);
291 }
93c5aa8a 292 else if (strcmp(argv[1], "io") == 0)
293 {
294 unsigned int cmd = 0, value, iosize;
295 unsigned char *p = data;
296
297 for (i = 2; i < argc; ) {
298 if (argv[i][0] == 'r')
299 cmd = IOSEQ_R8;
300 else if (argv[i][0] == 'w')
301 cmd = IOSEQ_W8;
302 else
303 usage(argv[0]);
304
305 iosize = atoi_or_die(&argv[i][1]);
306 if (iosize == 32)
307 cmd += 2;
308 else if (iosize == 16)
309 cmd += 1;
310 else if (iosize != 8)
311 usage(argv[0]);
312 *p++ = cmd;
313 i++;
314
315 addr = atoi_or_die(argv[i]);
316 *p++ = addr >> 16;
317 *p++ = addr >> 8;
318 *p++ = addr >> 0;
319 i++;
320
321 if (cmd == IOSEQ_W8 || cmd == IOSEQ_W16 || cmd == IOSEQ_W32) {
322 value = atoi_or_die(argv[i]);
323 switch (iosize) {
324 case 32:
325 *p++ = value >> 24;
326 *p++ = value >> 16;
327 case 16:
328 *p++ = value >> 8;
329 case 8:
330 *p++ = value >> 0;
331 }
332 i++;
333 }
334
335 count++;
336 }
337 }
4e6ba16d 338 else if (strcmp(argv[1], "loadstate") == 0)
339 {
340 unsigned char chunk;
341 char header[12];
342 gzFile f;
343 int len;
344
345 if (argc != 3)
346 usage(argv[0]);
347
348 f = gzopen(argv[2], "rb");
349 if (f == NULL) {
350 perror("gzopen");
351 return 1;
352 }
353
354 checked_gzread(f, header, sizeof(header));
355 if (strncmp(header, "PicoSEXT", 8) != 0) {
356 fprintf(stderr, "bad header\n");
357 return 1;
358 }
359
360 while (!gzeof(file))
361 {
362 ret = gzread(f, &chunk, 1);
363 if (ret == 0)
364 break;
365 checked_gzread(f, &len, 4);
366 //printf("%2d %x\n", chunk, len);
367 switch (chunk) {
368 case 3: // VRAM
369 checked_gzread(f, data, len);
370 size += len;
371 break;
372 case 5: // CRAM
373 checked_gzread(f, data + 0x10000, len);
374 size += len;
375 break;
376 case 6: // VSRAM
377 checked_gzread(f, data + 0x10080, len);
378 size += len;
379 break;
380 case 8: // video
381 checked_gzread(f, data + 0x10100, len);
382 data[size+0] &= ~1; // no display disable
383 data[size+1] |= 0x40; // no blanking
384 size += 0x20;
385 break;
386 default:
387 if (chunk > 64+8) {
388 fprintf(stderr, "bad chunk: %d\n", chunk);
389 return 1;
390 }
391 gzseek(f, len, SEEK_CUR);
392 break;
393 }
394 }
395 gzclose(f);
396 if (size != 0x10120) {
397 fprintf(stderr, "bad final size: %x\n", size);
398 return 1;
399 }
400 // unbyteswap *RAMs (stored byteswapped)
401 for (i = 0; i < 0x10100; i += 2) {
402 int tmp = data[i];
403 data[i] = data[i + 1];
404 data[i + 1] = tmp;
405 }
406 }
8689c962 407 else
408 usage(argv[0]);
409
410 ret = ioperm(PORT_DATA, 3, 1);
76ba9df5 411 if (ret != 0) {
412 perror("ioperm");
413 return 1;
414 }
415
416 signal(SIGINT, inthandler);
417
8689c962 418 printf("regs: %02x %02x %02x\n",
419 inb(PORT_DATA), inb(PORT_STATUS), inb(PORT_CONTROL));
420 outb(0xe8, PORT_CONTROL); /* TR low - request for transfer */
76ba9df5 421
8689c962 422 if (inb(PORT_STATUS) & 0x40)
423 printf("waiting for TH low..\n");
424 while (inb(PORT_STATUS) & 0x40)
272bd2ec 425 usleep(100000);
8689c962 426
427 outb(0xe0, PORT_CONTROL);
76ba9df5 428
8689c962 429 if (strcmp(argv[1], "send") == 0)
76ba9df5 430 {
8689c962 431 send_cmd(CMD_MD_SEND);
432 send_byte((addr >> 16) & 0xff);
433 send_byte((addr >> 8) & 0xff);
434 send_byte((addr >> 0) & 0xff);
435 send_byte((size >> 16) & 0xff);
436 send_byte((size >> 8) & 0xff);
437 send_byte((size >> 0) & 0xff);
438
76ba9df5 439 for (i = 0; i < size; i++)
440 {
441 if ((i & 0xff) == 0) {
442 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
443 printf("%06x/%06x", i, size);
444 fflush(stdout);
445 }
446
8689c962 447 send_byte(data[i]);
76ba9df5 448 }
76ba9df5 449 }
8689c962 450 else if (strcmp(argv[1], "recv") == 0)
76ba9df5 451 {
8689c962 452 send_cmd(CMD_MD_RECV);
453 send_byte((addr >> 16) & 0xff);
454 send_byte((addr >> 8) & 0xff);
455 send_byte((addr >> 0) & 0xff);
456 send_byte((size >> 16) & 0xff);
457 send_byte((size >> 8) & 0xff);
458 send_byte((size >> 0) & 0xff);
93c5aa8a 459 output_to_input();
76ba9df5 460
461 for (i = 0; i < size; i++)
462 {
463 if ((i & 0xff) == 0) {
464 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
465 printf("%06x/%06x", i, size);
466 fflush(stdout);
467 }
468
8689c962 469 data[i] = recv_byte();
76ba9df5 470 }
8689c962 471
472 fwrite(data, 1, size, file);
76ba9df5 473 }
272bd2ec 474 else if (strcmp(argv[1], "jump") == 0)
475 {
476 send_cmd(CMD_JUMP);
477 send_byte((addr >> 16) & 0xff);
478 send_byte((addr >> 8) & 0xff);
479 send_byte((addr >> 0) & 0xff);
480 }
93c5aa8a 481 else if (strcmp(argv[1], "io") == 0)
482 {
483 unsigned char *p = data;
484 unsigned char rdata[4];
485 send_cmd(CMD_IOSEQ);
486 send_byte((count >> 8) & 0xff);
487 send_byte((count >> 0) & 0xff);
488
489 for (; count > 0; count--) {
490 input_to_output();
491 send_bytes(p, 4); /* cmd + addr */
492
493 switch (p[0]) {
494 case IOSEQ_R8:
495 output_to_input();
496 recv_bytes(rdata, 1);
497 printf("r8 %06x %02x\n", PBE3(p + 1), rdata[0]);
498 p += 4;
499 break;
500 case IOSEQ_R16:
501 output_to_input();
502 recv_bytes(rdata, 2);
503 printf("r16 %06x %04x\n", PBE3(p + 1), PBE2(rdata));
504 p += 4;
505 break;
506 case IOSEQ_R32:
507 output_to_input();
508 recv_bytes(rdata, 4);
509 printf("r32 %06x %08x\n", PBE3(p + 1), PBE4(rdata));
510 p += 4;
511 break;
512 case IOSEQ_W8:
513 send_bytes(&p[4], 1);
514 printf("w8 %06x %02x\n", PBE3(p + 1), p[4]);
515 p += 5;
516 break;
517 case IOSEQ_W16:
518 send_bytes(&p[4], 2);
519 printf("w16 %06x %04x\n", PBE3(p + 1), PBE2(p + 4));
520 p += 6;
521 break;
522 case IOSEQ_W32:
523 send_bytes(&p[4], 4);
524 printf("w32 %06x %08x\n", PBE3(p + 1), PBE4(p + 4));
525 p += 8;
526 break;
527 default:
528 do_exit("error in ioseq data\n", NULL);
529 break;
530 }
531 }
532 }
4e6ba16d 533 else if (strcmp(argv[1], "loadstate") == 0)
534 {
535 send_cmd(CMD_LOADSTATE);
536
537 for (i = 0; i < size; i++)
538 {
539 if ((i & 0x1f) == 0) {
540 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
541 printf("%06x/%06x", i, size);
542 fflush(stdout);
543 }
544
545 send_byte(data[i]);
546 }
547 }
272bd2ec 548
4e6ba16d 549 if (size != 0) {
272bd2ec 550 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
551 printf("%06x/%06x\n", i, size);
272bd2ec 552 }
4e6ba16d 553 if (file != NULL)
554 fclose(file);
76ba9df5 555
8689c962 556 /* switch TL back to high, disable outputs */
557 outb(0xe0, PORT_CONTROL);
558
76ba9df5 559 return 0;
560}
561