megaed-sv: allow to input raw mapper code
[megadrive.git] / hexed / pc_transfer.c
index 844054f..bcfb652 100644 (file)
@@ -1,8 +1,39 @@
+/*
+ * Copyright (c) 2011, GraÅžvydas Ignotas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the organization nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 #include <sys/io.h>
 #include <signal.h>
+#include <sys/time.h>
+#include <zlib.h>
+
+#include "transfer.h"
 
 /*
  * PC:
  * 7 TH  --> 10 ACK
  * 8 GND --- 21 GND
  * 9 TR  <-- 17 /SLCT_IN
+ *
+ * start: TH low/high, TL high
+ *
+ * TH low  - lower nibble: MD ready to recv | MD sent to PC
+ * TL low  - lower niblle: sent to MD       | ready to recv from MD
+ * TH high - upper nibble: MD ready to recv | MD sent to PC
+ * TL high - upper nibble: sent             | ready to recv from MD
  */
 
-static void inthandler(int u)
+#define ACK_TIMEOUT    2000000
+
+#define PORT_DATA      888
+#define PORT_STATUS    889
+#define PORT_CONTROL   890
+
+#define timediff(now, start) \
+       ((now.tv_sec - start.tv_sec) * 1000000 + now.tv_usec - start.tv_usec)
+
+#define PBE2(p) ((*(p) << 8) | *(p+1))
+#define PBE3(p) ((*(p) << 16) | (*(p + 1) << 8) | *(p + 2))
+#define PBE4(p) ((*(p) << 24) | (*(p + 1) << 16) | (*(p + 2) << 8) | *(p + 3))
+
+static void do_exit(const char *msg, const char *where)
 {
        /* switch TL back to high */
-       outb(0xe0, 890);
-       printf("\n");
+       outb(0xe0, PORT_CONTROL);
+
+       if (where)
+               fprintf(stderr, "%s: ", where);
+       if (msg)
+               fprintf(stderr, "%s", msg);
        exit(1);
 }
 
-int main(int argc, char *argv[])
+static void inthandler(int u)
 {
-       int size, byte, ret, i;
-       unsigned char *data;
-       char *p = NULL;
-       FILE *file;
+       do_exit("\n", NULL);
+}
 
-       if (argc != 4 || argv[1][0] != '-' || (argv[1][1] != 'r' && argv[1][1] != 'w')) {
-               printf("usage:\n%s {-r,-w} <file> <size_hex>\n", argv[0]);
-               return 1;
+static void wait_th_low(const char *where)
+{
+       struct timeval start, now;
+
+       gettimeofday(&start, NULL);
+
+       while (inb(PORT_STATUS) & 0x40) {
+               gettimeofday(&now, NULL);
+               if (timediff(now, start) > ACK_TIMEOUT)
+                       do_exit("timeout waiting TH low\n", where);
        }
+}
 
-       file = fopen(argv[2], argv[1][1] == 'r' ? "wb" : "rb");
-       if (file == NULL) {
-               fprintf(stderr, "can't open file: %s\n", argv[2]);
-               return 1;
+static void wait_th_high(const char *where)
+{
+       struct timeval start, now;
+
+       gettimeofday(&start, NULL);
+
+       while (!(inb(PORT_STATUS) & 0x40)) {
+               gettimeofday(&now, NULL);
+               if (timediff(now, start) > ACK_TIMEOUT)
+                       do_exit("timeout waiting TH high\n", where);
        }
+}
+
+static void output_to_input(void)
+{
+       /* TL high, recv mode; also give time
+        * MD to see TL before we lower it in recv_byte */
+       outb(0xe0, PORT_CONTROL);
+       usleep(4*10);                   /* must be at least 12+8+8 M68k cycles, 28/7.67M */
+}
+
+static void input_to_output(void)
+{
+       wait_th_low("input_to_output");
+       outb(0xc0, PORT_CONTROL);       /* TL high, out mode */
+}
+
+static unsigned int recv_byte(void)
+{
+       unsigned int byte;
+
+       outb(0xe2, PORT_CONTROL);       /* TL low */
+
+       wait_th_low("recv_byte");
+
+       byte = inb(PORT_DATA) & 0x0f;
+
+       outb(0xe0, PORT_CONTROL);       /* TL high */
+
+       wait_th_high("recv_byte");
+
+       byte |= inb(PORT_DATA) << 4;
 
-       size = (int)strtoul(argv[3], &p, 16);
+       return byte;
+}
+
+static void recv_bytes(unsigned char *b, size_t count)
+{
+       while (count-- > 0)
+               *b++ = recv_byte();
+}
+
+static void send_byte(unsigned int byte)
+{
+       wait_th_low("recv_bytes");
+
+       outb(byte & 0x0f, PORT_DATA);
+       outb(0xc2, PORT_CONTROL);       /* TL low */
+
+       wait_th_high("recv_bytes");
+
+       outb((byte >> 4) & 0x0f, PORT_DATA);
+       outb(0xc0, PORT_CONTROL);       /* TL high */
+}
+
+static void send_bytes(unsigned char *b, size_t count)
+{
+       while (count-- > 0)
+               send_byte(*b++);
+}
+
+static void send_cmd(unsigned int cmd)
+{
+       send_byte(CMD_PREFIX);
+       send_byte(cmd);
+}
+
+static void usage(const char *argv0)
+{
+       fprintf(stderr, "usage:\n%s <cmd> [args]\n"
+               "\tsend <file> <addr> [size]\n"
+               "\trecv <file> <addr> <size>\n"
+               "\tjump <addr>\n"
+               "\tio {r{8,16,32} <addr>,w{8,16,32} <addr> <data>}*\n"
+               "\tloadstate <picodrive_savestate>\n"
+               "\trecvvram <file>\n", argv0);
+       exit(1);
+}
+
+static unsigned int atoi_or_die(const char *a)
+{
+       char *p = NULL;
+       unsigned int i;
+
+       i = strtoul(a, &p, 0);
        if (p == NULL || *p != 0) {
-               fprintf(stderr, "can't convert size %s\n", argv[3]);
-               return 1;
+               fprintf(stderr, "atoi: can't convert: %s\n", a);
+               exit(1);
        }
 
-       data = malloc(size);
+       return i;
+}
+
+static void checked_gzread(gzFile f, void *data, size_t size)
+{
+       unsigned int ret;
+       ret = gzread(f, data, size);
+       if (ret != size) {
+               fprintf(stderr, "gzread returned %d/%zu\n", ret, size);
+               exit(1);
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       unsigned int addr = 0, size = 0;
+       unsigned int count = 0, i = 0;
+       int ret;
+       unsigned char *data;
+       FILE *file = NULL;
+
+       if (argc < 2)
+               usage(argv[0]);
+
+       data = malloc(0x1000000);
        if (data == NULL) {
-               fprintf(stderr, "can't alloc %d bytes\n", size);
+               fprintf(stderr, "can't alloc %d bytes\n", 0x1000000);
                return 1;
        }
 
-       ret = ioperm(888, 3, 1);
+       /* parse args, read files.. */
+       if (strcmp(argv[1], "send") == 0)
+       {
+               if (argc != 4 && argc != 5)
+                       usage(argv[0]);
+
+               file = fopen(argv[2], "rb");
+               if (file == NULL) {
+                       fprintf(stderr, "can't open file: %s\n", argv[2]);
+                       return 1;
+               }
+
+               addr = atoi_or_die(argv[3]);
+               if (argv[4] == NULL) {
+                       fseek(file, 0, SEEK_END);
+                       size = ftell(file);
+                       fseek(file, 0, SEEK_SET);
+               }
+               else
+                       size = atoi_or_die(argv[4]);
+
+               ret = fread(data, 1, size, file);
+               if (ret != size) {
+                       fprintf(stderr, "fread returned %d/%d\n", ret, size);
+                       perror(NULL);
+                       return 1;
+               }
+       }
+       else if (strcmp(argv[1], "recv") == 0)
+       {
+               if (argc != 5)
+                       usage(argv[0]);
+
+               file = fopen(argv[2], "wb");
+               if (file == NULL) {
+                       fprintf(stderr, "can't open file: %s\n", argv[2]);
+                       return 1;
+               }
+
+               addr = atoi_or_die(argv[3]);
+               size = atoi_or_die(argv[4]);
+
+               memset(data, 0, size);
+       }
+       else if (strcmp(argv[1], "jump") == 0)
+       {
+               if (argc != 3)
+                       usage(argv[0]);
+
+               addr = atoi_or_die(argv[2]);
+       }
+       else if (strcmp(argv[1], "io") == 0)
+       {
+               unsigned int cmd = 0, value, iosize;
+               unsigned char *p = data;
+
+               for (i = 2; i < argc; ) {
+                       if (argv[i][0] == 'r')
+                               cmd = IOSEQ_R8;
+                       else if (argv[i][0] == 'w')
+                               cmd = IOSEQ_W8;
+                       else
+                               usage(argv[0]);
+
+                       iosize = atoi_or_die(&argv[i][1]);
+                       if (iosize == 32)
+                               cmd += 2;
+                       else if (iosize == 16)
+                               cmd += 1;
+                       else if (iosize != 8)
+                               usage(argv[0]);
+                       *p++ = cmd;
+                       i++;
+
+                       addr = atoi_or_die(argv[i]);
+                       *p++ = addr >> 16;
+                       *p++ = addr >> 8;
+                       *p++ = addr >> 0;
+                       i++;
+
+                       if (cmd == IOSEQ_W8 || cmd == IOSEQ_W16 || cmd == IOSEQ_W32) {
+                               value = atoi_or_die(argv[i]);
+                               switch (iosize) {
+                               case 32:
+                                       *p++ = value >> 24;
+                                       *p++ = value >> 16;
+                               case 16:
+                                       *p++ = value >> 8;
+                               case 8:
+                                       *p++ = value >> 0;
+                               }
+                               i++;
+                       }
+
+                       count++;
+               }
+       }
+       else if (strcmp(argv[1], "loadstate") == 0)
+       {
+               unsigned char chunk;
+               char header[12];
+               gzFile f;
+               int len;
+
+               if (argc != 3)
+                       usage(argv[0]);
+
+               f = gzopen(argv[2], "rb");
+               if (f == NULL) {
+                       perror("gzopen");
+                       return 1;
+               }
+
+               checked_gzread(f, header, sizeof(header));
+               if (strncmp(header, "PicoSEXT", 8) != 0) {
+                       fprintf(stderr, "bad header\n");
+                       return 1;
+               }
+
+               while (!gzeof(file))
+               {
+                       ret = gzread(f, &chunk, 1);
+                       if (ret == 0)
+                               break;
+                       checked_gzread(f, &len, 4);
+                       //printf("%2d %x\n", chunk, len);
+                       switch (chunk) {
+                       case 3: // VRAM
+                               checked_gzread(f, data, len);
+                               size += len;
+                               break;
+                       case 5: // CRAM
+                               checked_gzread(f, data + 0x10000, len);
+                               size += len;
+                               break;
+                       case 6: // VSRAM
+                               checked_gzread(f, data + 0x10080, len);
+                               size += len;
+                               break;
+                       case 8: // video
+                               checked_gzread(f, data + 0x10100, len);
+                               data[size+0] &= ~1;   // no display disable
+                               data[size+1] |= 0x40; // no blanking
+                               size += 0x20;
+                               break;
+                       default:
+                               if (chunk > 64+8) {
+                                       fprintf(stderr, "bad chunk: %d\n", chunk);
+                                       return 1;
+                               }
+                               gzseek(f, len, SEEK_CUR);
+                               break;
+                       }
+               }
+               gzclose(f);
+               if (size != 0x10120) {
+                       fprintf(stderr, "bad final size: %x\n", size);
+                       return 1;
+               }
+               // unbyteswap *RAMs (stored byteswapped)
+               for (i = 0; i < 0x10100; i += 2) {
+                       int tmp = data[i];
+                       data[i] = data[i + 1];
+                       data[i + 1] = tmp;
+               }
+       }
+       else if (strcmp(argv[1], "recvvram") == 0)
+       {
+               if (argc != 3)
+                       usage(argv[0]);
+
+               file = fopen(argv[2], "wb");
+               if (file == NULL) {
+                       fprintf(stderr, "can't open file: %s\n", argv[2]);
+                       return 1;
+               }
+
+               size = 0x10000;
+               memset(data, 0, size);
+       }
+       else
+               usage(argv[0]);
+
+       ret = ioperm(PORT_DATA, 3, 1);
        if (ret != 0) {
                perror("ioperm");
                return 1;
@@ -74,16 +430,38 @@ int main(int argc, char *argv[])
 
        signal(SIGINT, inthandler);
 
-       printf("regs: %02x %02x %02x\n", inb(888), inb(889), inb(890));
-       outb(0xe0, 890);
+       printf("regs: %02x %02x %02x\n",
+               inb(PORT_DATA), inb(PORT_STATUS), inb(PORT_CONTROL));
+
+       /* wait for start condition */
+       if (!(inb(PORT_STATUS) & 0x40))
+               printf("waiting for TH high..\n");
+       while (!(inb(PORT_STATUS) & 0x40))
+               usleep(10000);
 
-       while (!(inb(889) & 0x40)) {
-               printf("waiting for TH..\n");
-               sleep(5);
+       outb(0xe8, PORT_CONTROL);       /* TR low - request for transfer */
+
+       /* wait for request ack */
+       if (inb(PORT_STATUS) & 0x40)
+               printf("waiting for TH low..\n");
+       for (i = 10000; inb(PORT_STATUS) & 0x40; i += 100) {
+               if (i > 100000)
+                       i = 100000;
+               usleep(i);
        }
 
-       if (argv[1][1] == 'r')
+       outb(0xe0, PORT_CONTROL);
+
+       if (strcmp(argv[1], "send") == 0)
        {
+               send_cmd(CMD_PC_SEND);
+               send_byte((addr >> 16) & 0xff);
+               send_byte((addr >>  8) & 0xff);
+               send_byte((addr >>  0) & 0xff);
+               send_byte((size >> 16) & 0xff);
+               send_byte((size >>  8) & 0xff);
+               send_byte((size >>  0) & 0xff);
+
                for (i = 0; i < size; i++)
                {
                        if ((i & 0xff) == 0) {
@@ -92,60 +470,135 @@ int main(int argc, char *argv[])
                                fflush(stdout);
                        }
 
-                       outb(0xe2, 890);        /* TL low */
-
-                       /* wait for TH low */
-                       while (inb(889) & 0x40) ;
-
-                       byte = inb(888) & 0x0f;
-
-                       outb(0xe0, 890);        /* TL high */
+                       send_byte(data[i]);
+               }
+       }
+       else if (strcmp(argv[1], "recv") == 0)
+       {
+               send_cmd(CMD_PC_RECV);
+               send_byte((addr >> 16) & 0xff);
+               send_byte((addr >>  8) & 0xff);
+               send_byte((addr >>  0) & 0xff);
+               send_byte((size >> 16) & 0xff);
+               send_byte((size >>  8) & 0xff);
+               send_byte((size >>  0) & 0xff);
+               output_to_input();
 
-                       /* wait for TH high */
-                       while (!(inb(889) & 0x40)) ;
+               for (i = 0; i < size; i++)
+               {
+                       if ((i & 0xff) == 0) {
+                               printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
+                               printf("%06x/%06x", i, size);
+                               fflush(stdout);
+                       }
 
-                       byte |= inb(888) << 4;
-                       data[i] = byte;
+                       data[i] = recv_byte();
                }
 
                fwrite(data, 1, size, file);
        }
-       else
+       else if (strcmp(argv[1], "jump") == 0)
        {
-               ret = fread(data, 1, size, file);
-               if (ret < size)
-                       printf("warning: read only %d/%d\n", ret, size);
+               send_cmd(CMD_JUMP);
+               send_byte((addr >> 16) & 0xff);
+               send_byte((addr >>  8) & 0xff);
+               send_byte((addr >>  0) & 0xff);
+       }
+       else if (strcmp(argv[1], "io") == 0)
+       {
+               unsigned char *p = data;
+               unsigned char rdata[4];
+               send_cmd(CMD_IOSEQ);
+               send_byte((count >> 8) & 0xff);
+               send_byte((count >> 0) & 0xff);
 
-               outb(0xc0, 890);        /* out mode, TL hi */
-               outb(data[0] & 0x0f, 888);
-               outb(0xc2, 890);        /* out mode, TL low (start condition) */
+               for (; count > 0; count--) {
+                       input_to_output();
+                       send_bytes(p, 4);       /* cmd + addr */
+
+                       switch (p[0]) {
+                       case IOSEQ_R8:
+                               output_to_input();
+                               recv_bytes(rdata, 1);
+                               printf("r8  %06x       %02x\n", PBE3(p + 1), rdata[0]);
+                               p += 4;
+                               break;
+                       case IOSEQ_R16:
+                               output_to_input();
+                               recv_bytes(rdata, 2);
+                               printf("r16 %06x     %04x\n", PBE3(p + 1), PBE2(rdata));
+                               p += 4;
+                               break;
+                       case IOSEQ_R32:
+                               output_to_input();
+                               recv_bytes(rdata, 4);
+                               printf("r32 %06x %08x\n", PBE3(p + 1), PBE4(rdata));
+                               p += 4;
+                               break;
+                       case IOSEQ_W8:
+                               send_bytes(&p[4], 1);
+                               printf("w8  %06x       %02x\n", PBE3(p + 1), p[4]);
+                               p += 5;
+                               break;
+                       case IOSEQ_W16:
+                               send_bytes(&p[4], 2);
+                               printf("w16 %06x     %04x\n", PBE3(p + 1), PBE2(p + 4));
+                               p += 6;
+                               break;
+                       case IOSEQ_W32:
+                               send_bytes(&p[4], 4);
+                               printf("w32 %06x %08x\n", PBE3(p + 1), PBE4(p + 4));
+                               p += 8;
+                               break;
+                       default:
+                               do_exit("error in ioseq data\n", NULL);
+                               break;
+                       }
+               }
+       }
+       else if (strcmp(argv[1], "loadstate") == 0)
+       {
+               send_cmd(CMD_LOADSTATE);
 
                for (i = 0; i < size; i++)
                {
-                       if ((i & 0xff) == 0) {
+                       if ((i & 0x1f) == 0) {
                                printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
                                printf("%06x/%06x", i, size);
                                fflush(stdout);
                        }
 
-                       /* wait for TH low */
-                       while (inb(889) & 0x40) ;
+                       send_byte(data[i]);
+               }
+       }
+       else if (strcmp(argv[1], "recvvram") == 0)
+       {
+               send_cmd(CMD_VRAM_RECV);
+               output_to_input();
 
-                       byte = data[i];
+               for (i = 0; i < size; i++)
+               {
+                       if ((i & 0xff) == 0) {
+                               printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
+                               printf("%06x/%06x", i, size);
+                               fflush(stdout);
+                       }
 
-                       outb(byte & 0x0f, 888);
-                       outb(0xc2, 890);        /* TL low */
+                       data[i] = recv_byte();
+               }
 
-                       /* wait for TH high */
-                       while (!(inb(889) & 0x40)) ;
+               fwrite(data, 1, size, file);
+       }
 
-                       outb(byte >> 4, 888);
-                       outb(0xc0, 890);        /* TL high */
-               }
+       if (size != 0) {
+               printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
+               printf("%06x/%06x\n", i, size);
        }
-       printf("\b\b\b\b\b\b\b\b\b\b\b\b\b");
-       printf("%06x/%06x\n", i, size);
-       fclose(file);
+       if (file != NULL)
+               fclose(file);
+
+       /* switch TL back to high, disable outputs */
+       outb(0xe0, PORT_CONTROL);
 
        return 0;
 }