/*****************************************************************************

			       XCopilot

This code is part of XCopilot, a port of copilot

		  Copyright (C) 1997 Ivan A. Curtis
		       icurtis@radlogic.com.au

The original MS-Windows95 copilot emulator was written by Greg Hewgill.
The following copyright notice appeared on the original copilot sources:

		  Copyright (c) 1996 Greg Hewgill

 MC68000 Emulation code is from Bernd Schmidt's Unix Amiga Emulator.
       The following copyright notice appeared in those files:

	  Original UAE code Copyright (c) 1995 Bernd Schmidt

This code must not be distributed without these copyright notices intact.

*******************************************************************************
*******************************************************************************

Filename:	main.c

Description:	main file for xcopilot emulator

Update History:   (most recent first)
   Ian Goldberg   25-Sep-97 11:09 -- rewrite of serial and gdb support
   Ian Goldberg   11-Sep-97 09:48 -- added bus error support
   Jeff Dionne    10-Jul-97 10:00 -- added support for gdb debugging via TCP
   Ian Goldberg   06-Jul-97 17:34 -- added support for serial I/O via a pty
   Brian Grossman 30-Jun-97 24:00 -- added pixeldoubling
   Ian Goldberg   18-Apr-97 11:13 -- added support for gdb debugging via a pty
   I. Curtis       9-Apr-97 11:42 -- v0.4
   I. Curtis      18-Mar-97 14:00 -- v0.3
   I. Curtis      26-Feb-97 13:52 -- first release
   I. Curtis      23-Feb-97 20:43 -- Created.

******************************************************************************/

#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#ifdef NEED_SELECT_H
#include <sys/select.h>
#endif
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "sysdeps.h"
#include "shared.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "dragonball.h"

#include "display.h"
#include "pilotcpu.h"
#include "pdebug.h"
#include "version.h"

#define RAMSIZE 1024		/* this is in k bytes */

char *id_version = "XCopilot "XCOPILOT_VERSION;

int tcp_open(char *name);

#ifdef __sgi__
#include <sys/time.h>
void usleep(long usecs) {
  struct timeval tv;
  tv.tv_sec  = usecs / 1000000L;
  tv.tv_usec = usecs % 1000000L;
  select(0, 0, 0, 0, &tv);
}
#endif

/*
 * These error messages correspond to the
 * codes in memory.h
 */
static const char *CpuInitErrors[] = {
  "",
  "pilot.rom file not found",
  "Error loading pilot.rom file",
  "Error loading pilot ram file",
};
/*****************************************************************************
 *                                                                           *
 * 			   Global variables                                  *
 *                                                                           *
 *****************************************************************************/
static shared_img *shptr;	/* pointer to the shared registers block */
char *BackgroundColorName = NULL;
char *BacklightColorName = NULL;


/*********************************************
 * attach to the shared memory blocks        *
 * for the main memory and for the registers *
 * of the custom block                       *
 *********************************************/
void attach_shm(int shmid)
{
  void *s;
  s = shmat(shmid, (char *)0, 0);
  if (s == (void *)-1) {
    perror("shmat");
    exit(1);
  }
  shptr = (shared_img *)s;
}

/*************************************
 * This is the CPU emulation process *
 *************************************/
void cpu_proc(int shmid, int Debugging, char *DebugArgs)
{
  int r;
  attach_shm(shmid); /* get shared memory */
  r = CPU_init(shptr, RAMSIZE);
  if (r != 0) {
    fprintf(stderr, "E - %s\n", CpuInitErrors[r]);
    exit(1);
  }

  /*
   *   fprintf(stderr, "I - calling CPU..\n");
   *   fflush(stderr);
   */
  CPU(shptr);
  shmdt((char *)shptr);
}

/*************************************
 * This is the LCD emulation process *
 *************************************/
void lcd_proc(int shmid, int no_x_shm, int pixeldouble)
{
  char *sbuf;
  attach_shm(shmid);                       /* get shared memory */
  memory_init(RAMSIZE);		                /* initialize memory */
  sbuf = xcpInitialize(no_x_shm, pixeldouble); /* init xwindows */
  xcpEventLoop(sbuf, shptr);
  shmdt((char *)shptr);
}

/********************************
 * This is the Debugger process *
 ********************************/
#define DEBUG_PORTNUM 2000
void debug_proc(int shmid, char *DebugArgs)
{
  attach_shm(shmid);		/* get shared memory */
  memory_init(RAMSIZE);		/* initialize memory */
  MC68000_setshare(shptr);	/* initialize Shptr in newcpu module */

  /* Old-style debugging on port 2000 */
  pdebug_loop(DebugArgs, DEBUG_PORTNUM, shptr);
  shptr->CpuReq = cpuExit;

  shmdt((char *)shptr);
}

static int correct_write(int fd, unsigned char *buf, int amt)
{
    int wrote = 0;

    while (amt) {
	int res = write(fd, buf, amt);
	if (res <= 0) return res;
	wrote += res;
	buf += res;
	amt -= res;
    }
    return wrote;
}

static void attach_serial(char *SerialArgs, int *readfd, int *writefd)
{
  /* Attach to the pty for serial I/O */
  if (SerialArgs) {
    close(0);
    close(1);
    if (open(SerialArgs, O_RDWR) != 0) {
	fprintf(stderr, "Error opening %s\n", SerialArgs);
	perror("read");
	exit(1);
    }
    if (dup2(0,1) != 1) {
	fprintf(stderr, "Error opening %s\n", SerialArgs);
	perror("dup2");
	exit(1);
    }
    fprintf(stderr, "Serial I/O on %s\n", SerialArgs);
  }
  *readfd = 0;
  *writefd = 1;
}

static void attach_gdb(char *DebugArgs, int *gdbfd)
{
    int gdb_fd;

    /* Get a fd for talking to gdb, if necessary */
    if (!DebugArgs) DebugArgs = "/dev/ptyqf";
    if (strchr(DebugArgs, ':')) {
        /* gdb on a TCP port, actually */
        gdb_fd = tcp_open(DebugArgs);
    } else {
        gdb_fd = open(DebugArgs, O_RDWR);
    }
    if (gdb_fd < 0) {
	perror("open");
	fprintf(stderr, "Debugging failed on %s; "
	                "resuming without debugging.\n", DebugArgs);
    } else {
	fprintf(stderr, "Debugging on %s\n", DebugArgs);
    }
    *gdbfd = gdb_fd;
}

static void set_baud(int fd, int baudrate)
{
    struct termios tempio;
    unsigned int brate;

    /* get current setting */
    if (tcgetattr(fd, &tempio) < 0) {
	/* We can't do this on this kind of fd */
	return;
    }
    switch (baudrate) {
    case 300:
	brate = B300;
	break;
    case 1200:
	brate = B1200;
	break;
    case 2400:
	brate = B2400;
	break;
    case 4800:
	brate = B4800;
	break;
    case 9600:
	brate = B9600;
	break;
    case 19200:
	brate = B19200;
	break;
    case 38400:
	brate = B38400;
	break;
    default:
	fprintf(stderr, "E - unsupported baudrate %d; using 38400\n",
			    baudrate);
	brate = B38400;
    }
    tempio.c_iflag = 0;
    tempio.c_oflag = 0;
    tempio.c_cflag |= CLOCAL;
    tempio.c_lflag = 0;
    tempio.c_cc[VMIN] = 1;
    tempio.c_cc[VTIME] = 0;
    if (cfsetispeed(&tempio, brate) < 0) {
	perror("E - cfsetispeed");
    }
    if (cfsetospeed(&tempio, brate) < 0) {
	perror("E - cfsetospeed");
    }
    if (tcsetattr(fd, TCSANOW, &tempio) < 0) {
	perror("E - tcsetattr");
    }
}

static void set_flags(int fd, int flags)
{
    struct termios tempio;

    /* get current setting */
    if (tcgetattr(fd, &tempio) < 0) {
	/* We can't do this on this kind of fd */
	return;
    }
    tempio.c_cflag &= ~(CSIZE|CSTOPB|PARENB|PARODD);
    if (flags & 1) {
	tempio.c_cflag |= CS8;
    } else {
	tempio.c_cflag |= CS7;
    }
    if (flags & 2) {
	tempio.c_cflag |= CSTOPB;
    }
    if (flags & 4) {
	tempio.c_cflag |= PARODD;
    }
    if (flags & 8) {
	tempio.c_cflag |= PARENB;
    }
    if (tcsetattr(fd, TCSANOW, &tempio) < 0) {
	perror("E - tcsetattr");
    }
}

/******************************************
 * This is the serial and gdb I/O process *
 ******************************************/
void serial_proc(int serial_towrite, int gdb_towrite, int Debugging,
		 char *SerialArgs, char *DebugArgs)
{
    fd_set readers, orig_readers;
    struct timeval tv;
    int maxfd;
    int serial_read = 0, serial_write = 1, gdb_fd = -1;
    int serial_baud = 0, serial_flags = 0;

    /* WARNING: This uses a shared memory data structure to store the FIFO.
	      The consumer is getting things from this _at the same time_ as
	      this is producing.  Examine custom.c and take a course in
	      concurrent programming before modifying this.  :-) - Ian */

    attach_serial(SerialArgs, &serial_read, &serial_write);
    if (Debugging == 2) attach_gdb(DebugArgs, &gdb_fd);

    FD_ZERO(&orig_readers);
    FD_SET(serial_read, &orig_readers);
    maxfd = serial_read;
    FD_SET(serial_towrite, &orig_readers);
    if (serial_towrite > maxfd) maxfd = serial_towrite;
    if (gdb_fd >= 0) {
	FD_SET(gdb_fd, &orig_readers);
	if (gdb_fd > maxfd) maxfd = gdb_fd;
    }
    if (gdb_towrite >= 0) {
	FD_SET(gdb_towrite, &orig_readers);
	if (gdb_towrite > maxfd) maxfd = gdb_towrite;
    }
    ++maxfd;

    while(shptr->CpuReq != cpuExit) {
	int selres;
	unsigned char buf[FIFO_SIZE];
	
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	readers = orig_readers;

	selres = select(maxfd, &readers, NULL, NULL, &tv);
	if (selres < 0) {
	    perror("select");
	    return;
	}
	if (selres > 0) {
	    /* There's something ready to go. */
	    int res;

	    if (serial_towrite >= 0 && FD_ISSET(serial_towrite, &readers)) {
		/* The CPU wants to write to the serial port */
                res = read(serial_towrite, buf, FIFO_SIZE);
		if (res > 0) {
		    /* Check the baud rate */
		    if (serial_baud != shptr->serial_baud) {
			set_baud(serial_write, shptr->serial_baud);
			serial_baud = shptr->serial_baud;
		    }
		    if (serial_flags != shptr->serial_flags) {
			set_flags(serial_write, shptr->serial_flags);
			serial_flags = shptr->serial_flags;
		    }
		    if (correct_write(serial_write, buf, res) <= 0) {
			attach_serial(SerialArgs, &serial_read, &serial_write);
		    }
		}
	    }

	    if (gdb_towrite >= 0 && FD_ISSET(gdb_towrite, &readers)) {
		/* The CPU wants to write to the gdb port */
		res = read(gdb_towrite, buf, FIFO_SIZE);
		if (res > 0) {
		    if (correct_write(gdb_fd, buf, res) <= 0) {
			if (Debugging == 2) {
			    close(gdb_fd);
			    attach_gdb(DebugArgs, &gdb_fd);
			}
		    }
		}
	    }

	    if (serial_read >= 0 && FD_ISSET(serial_read, &readers)) {
		/* There is data available on the serial port */
		int curhead = shptr->serial.head;
		int curtail = shptr->serial.tail;
		if (curtail >= curhead &&
		    (curtail != FIFO_SIZE-1 || curhead != 0)) {
		    res = read(serial_read,
				shptr->serial.fifo+curtail, FIFO_SIZE-curtail);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->serial.tail = curtail;
		    } else {
			attach_serial(SerialArgs, &serial_read, &serial_write);
		    }
		} else if (curtail+1 < curhead) {
		    res = read(serial_read,
				shptr->serial.fifo+curtail, curhead-curtail-1);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->serial.tail = curtail;
		    } else {
			attach_serial(SerialArgs, &serial_read, &serial_write);
		    }
		}
	    }

	    if (gdb_fd >= 0 && FD_ISSET(gdb_fd, &readers)) {
		/* There is data available on the gdb port */
		int curhead = shptr->gdb.head;
		int curtail = shptr->gdb.tail;
		if (curtail >= curhead && 
		    (curtail != FIFO_SIZE-1 || curhead != 0)) {
		    res = read(gdb_fd,
				shptr->gdb.fifo+curtail, FIFO_SIZE-curtail);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->gdb.tail = curtail;
		    } else if (Debugging == 2) {
			close(gdb_fd);
			attach_gdb(DebugArgs, &gdb_fd);
		    }
		} else if (curtail+1 < curhead) {
		    res = read(gdb_fd,
				shptr->gdb.fifo+curtail, curhead-curtail-1);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->gdb.tail = curtail;
		    } else if (Debugging == 2) {
			close(gdb_fd);
			attach_gdb(DebugArgs, &gdb_fd);
		    }
		}
	    }
	}
    }
}

void print_usage(char *name)
{
  fprintf(stderr, "Usage: %s [-uU] [-debug] [-bg color] [-bbg color] [-noxshm] [-double] [-serial]\n", name);
  fprintf(stderr, "\t-debug [args] => enter debug mode\n");
  fprintf(stderr, "\t-gdebug [args] => enter gdb debugging mode\n");
  fprintf(stderr, "\t-bg color => use named color for background in display\n");
  fprintf(stderr, "\t-bbg color => use named color for backlight background in display\n");
  fprintf(stderr, "\t-noxshm => don\'t use X Shared Mem, even if available\n");
  fprintf(stderr, "\t-double => use double-size pixels\n");
  fprintf(stderr, "\t-serial [ptyname] => use the given pty for serial I/O\n");
}

/*****************************************************************************
 *                                                                           *
 * 		     Main - Execution starts here                            *
 *                                                                           *
 *****************************************************************************/
int main(int argc, char *argv[])
{
  int Debugging;		/* if TRUE, enter debugger */
  char *DebugArgs = NULL;
  char *SerialArgs = NULL;
  int NoXShm;			/* if TRUE, don't use X Shared Mem */
  int cpu_pid, display_pid, debug_pid;
  int shmid;
  int PixelDouble;		/* if TRUE, do pixel doubling */
  void *s;
  int serial_pipefd[2];
  int gdb_pipefd[2];

  /*
   *   fprintf(stderr, "%s\n", id_version);
   */

  /*
   * Process command line args
   */
  {
    int argno;
    Debugging = 0;
    NoXShm = 0;
    PixelDouble = 0;
    for (argno = 1; argno < argc; argno ++) {
      if (strcmp(argv[argno], "-u") == 0 || strcmp(argv[argno], "-U") == 0) {
	print_usage(argv[0]);
	exit(1);
      } else if (strcmp(argv[argno], "-debug") == 0) {
	Debugging = 1;
	if (argno+1 < argc && argv[argno+1][0] != '-') {
	    DebugArgs = argv[argno+1];
	    ++argno;
	}
      } else if (strcmp(argv[argno], "-gdebug") == 0) {
	Debugging = 2;
	if (argno+1 < argc && argv[argno+1][0] != '-') {
	    DebugArgs = argv[argno+1];
	    ++argno;
	}
      } else if (strcmp(argv[argno], "-bg") == 0) {
	if (argno+1 < argc && argv[argno+1][0] != '-') {
	    BackgroundColorName = argv[argno+1];
	    ++argno;
	} else {
	    fprintf(stderr, "-bg option requires a color name "
	                    "as a parameter\n");
	    print_usage(argv[0]);
	    exit(1);
	}
      } else if (strcmp(argv[argno], "-bbg") == 0) {
	if (argno+1 < argc && argv[argno+1][0] != '-') {
	    BacklightColorName = argv[argno+1];
	    ++argno;
	} else {
	    fprintf(stderr, "-bbg option requires a color name "
	                    "as a parameter\n");
	    print_usage(argv[0]);
	    exit(1);
	}
      } else if (strcmp(argv[argno], "-noxshm") == 0) {
	NoXShm = 1;
      } else if (strcmp(argv[argno], "-double") == 0) {
	PixelDouble = 1;
      } else if (strcmp(argv[argno], "-serial") == 0) {
	if (argno+1 < argc && argv[argno+1][0] != '-') {
	    SerialArgs = argv[argno+1];
	    ++argno;
	} else {
	    SerialArgs = "/dev/ptyqe";
	}
      } else {
	fprintf(stderr, "E - unknown arg \"%s\"\n", argv[argno]);
	print_usage(argv[0]);
	exit(1);
      }
    }
  }

  /*
   * get a shared memory segment
   */
  shmid = shmget((key_t) 0, sizeof(shared_img), IPC_CREAT | 0777);
  if (shmid < 0) {
    perror("shmget");
    exit(1);
  }

  /* Init the shm segment */
  s = shmat(shmid, (char *)0, 0);
  if (s == (void *)-1) {
    perror("shmat");
    exit(1);
  }
  shptr = (shared_img *)s;
  shptr->run_updateisr = 0;
  shptr->pen = 0;
  shptr->pendown = 0;
  shptr->CpuReq = cpuNone;
  shptr->LcdReq = lcdNone;
  shptr->logF = NULL;
  shptr->dolog = 0;
  shptr->kbin = 0;
  shptr->kbout = 0;
  shptr->serial.head = 0;
  shptr->serial.tail = 0;
  shptr->gdb.head = 0;
  shptr->gdb.tail = 0;
  shptr->serial_baud = 0;
  shptr->serial_flags = 0;
  shptr->allowromwrites = 0;

  /* Set up the pipes */
  if (pipe(serial_pipefd) < 0) {
    perror("pipe");
    exit(1);
  }
  shptr->serial_writefd = serial_pipefd[1];
  if (Debugging == 2) {
    if (pipe(gdb_pipefd) < 0) {
      perror("pipe");
      exit(1);
    }
    shptr->gdb_writefd = gdb_pipefd[1];
  } else {
    shptr->gdb_writefd = -1;
  }

  /*
   * fork off the display process
   */
  display_pid = fork();
  if (display_pid < 0) {
    perror("display fork");
    exit(1);
  } else if (display_pid == 0) {
    /* The display does not need any of the pipes */
    close(0);
    close(1);
    close(serial_pipefd[0]);
    close(serial_pipefd[1]);
    if (Debugging == 2) {
	close(gdb_pipefd[0]);
	close(gdb_pipefd[1]);
    }
    lcd_proc(shmid, NoXShm, PixelDouble);
    exit(0);
  }

  /*
   * fork off the cpu process
   */
  cpu_pid = fork();
  if (cpu_pid < 0) {
    perror("cpu fork");
    exit(1);
  } else if (cpu_pid == 0) {
    /* The CPU does not need some of the pipes */
    close(0);
    close(1);
    close(serial_pipefd[0]);
    if (Debugging == 2) {
	close(gdb_pipefd[0]);
    }
    cpu_proc(shmid, Debugging, DebugArgs);
    exit(0);
  }

  if (Debugging == 1) {
    /*
    * fork of the debug process
    */
    debug_pid = fork();
    if (debug_pid < 0) {
	perror("debug fork");
	exit(1);
    } else if (debug_pid == 0) {
	/* The debugger does not need any of the pipes */
	close(0);
	close(1);
	close(serial_pipefd[0]);
	close(serial_pipefd[1]);
	if (Debugging == 2) {
	    close(gdb_pipefd[0]);
	    close(gdb_pipefd[1]);
	}
	debug_proc(shmid, DebugArgs);
	exit(0);
    }
  }

  /* Start the CPU */
  CPU_start(shptr);

  /* Handle the serial and gdb I/O */
  /* The serial proc does not need some of the pipes */
  close(serial_pipefd[1]);
  if (Debugging == 2) {
      close(gdb_pipefd[1]);
  }
  serial_proc(serial_pipefd[0], Debugging == 2 ? gdb_pipefd[0] : -1,
	      Debugging, SerialArgs, DebugArgs);

  /* Delete the shared mem */
  shmdt((char *)shptr);
  shmctl(shmid, IPC_RMID, 0);

  exit(0);
}
