/* RISC OS emulation via QEMU.  */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

extern "C" {
#include "qemu.h"
}
#include "riscos.h"

#include <string>

#define DEBUG_LOGFILE "/tmp/qemu.log"


RISCOS *riscos;

bool filetype_handling;

/* XXX: currently only used for async signals (see signal.c) */
CPUState *global_env;
/* used only if single thread */
CPUState *cpu_single_env = NULL;

/* used to free thread contexts */
TaskState *first_task_state;


/* Returns non-zero value when address range <lower> - <upper> (excl) is
   a valid address range.  */
static inline int
__valid_address (const void *lower, const void *upper)
{
  int flags;
  return 1;
}


static void write_backtrace (CPUARMState *env)
{
  int features = 0;
  int *fp = (int *) env->regs[11];
  int *oldfp = NULL;

  /* The ASM version did originally disable environment handlers
     but this seems to cause problems.  */

  fprintf(stderr, "\nStack backtrace:\n\n");

  /* fp[0]  => pc
     fp[-1] => lr
     fp[-2] => sp
     fp[-3] => previous fp  */
  while (fp != NULL)
    {
      int *pc, *lr;

      /* Check that FP is different.  */
      if (fp == oldfp)
	{
	  fprintf(stderr, "fp unchanged at %x\n", (int)fp);
	  break;
	}

      /* Validate FP address.  */
      if (!__valid_address(fp - 3, fp))
	{
	  fprintf(stderr, "Stack frame has gone out of bounds "
			  "with address %x\n", (int)fp - 12);
	  break;
	}

      /* Retrieve PC counter.  */
      pc = (int *)(fp[0] & 0xfffffffc);
      if (!(features & 0x8))
	pc += 1;

      if (!__valid_address(pc, pc))
	{
	  fprintf(stderr, "Invalid pc address %x\n", (int)pc);
	  break;
	}

      /* Retrieve lr.  */
      lr = (int *)(fp[-1] & 0xfffffffc);

      fprintf(stderr, "  (%8x) pc: %8x lr: %8x sp: %8x ",
	      (int)fp, (int)pc, (int)lr, fp[-2]);

      /* Retrieve function name.  */
      if (!__valid_address(pc - 7, pc))
	{
	  fputs("[invalid address]\n", stderr);
	}
      else
	{
	  int address;
	  const char *name = NULL;

	  for (address = -3; address > -8; address--)
	    {
	      if ((pc[address] & 0xffffff00) == 0xff000000)
		{
		  name = (const char *)(pc + address) - (pc[address] & 0xff);
		  break;
		}
	    }

	  /* Function name sanity check.  */
	  if (name != NULL
	      && (!__valid_address(name, (name + 256))
		  || strnlen(name, 256) == 256))
	    name = NULL;

	  if (!name)
	    fputs(" ?()\n", stderr);
	  else
	    fprintf(stderr, " %s()\n", name);
	}

      oldfp = fp;
      fp = (int *)fp[-3];

#if 0
      if (fp == __ul_callbackfp && fp != NULL)
	{
	  int reg;
	  const char * const rname[16] =
	    { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4",
	      "v5", "v6", "sl", "fp", "ip", "sp", "lr", "pc" };

	  /* At &oldfp[1] = cpsr, a1-a4, v1-v6, sl, fp, ip, sp, lr, pc */
	  fprintf(stderr, "\n  Register dump at %p:\n", &oldfp[1]);

	  if (__valid_address(oldfp, oldfp + 17))
	    {
	    for (reg = 0; reg < 16; reg++)
	      {
		if ((reg & 0x3) == 0)
		  fputs("\n   ", stderr);

		fprintf(stderr, " %s: %8x", rname[reg], oldfp[reg + 2]);
	      }

	    fprintf(stderr, "\n  cpsr: %8x\n\n", oldfp[1]);
	    }
	  else
	    fputs("    [bad register dump address]\n\n", stderr);
	}
#endif
    }

  fputs("\n", stderr);
}

#define MASK_CPDO               0x0e000000  /* arithmetic opcode */
#define MASK_CPDT               0x0c000000  /* data processing opcode */
#define MASK_CPRT               0x0e000010  /* register transfer opcode */

static const uint16_t cond_table[] =
  {
    0xf0f0, 0x0f0f, 0xcccc, 0x3333,
    0xff00, 0x00ff, 0xaaaa, 0x5555,
    0x0c0c, 0xf3f3, 0xaa55, 0x55aa,
    0x0a05, 0xf5fa, 0xffff, 0x0000
  };


void cpu_loop (CPUARMState *env)
{
  bool exception = false;
  while (1)
    {
      int trapnr = cpu_arm_exec (env);

      // Set to true if exception was successfully handled
      bool handled = false;

#if 0
      if (trapnr == EXCP_UDEF)
	{
	  FPA11 *qfpa = &((TaskState *) env->opaque)->fpa;
	  uint32_t *pc = (uint32_t *) env->regs[15];
	  uint32_t opcode = *pc;
	  if (EmulateAll (*pc, qfpa, env->regs))
	    {
	      handled = true;
	      env->regs[15] += 4;
	    }
	}
#endif
#if 1
      if (trapnr == EXCP_UDEF)
	{
	  FPA11 *qfpa = &((TaskState *) env->opaque)->fpa;
	  uint32_t *pc = (uint32_t *) env->regs[15];
	  uint32_t cpsr = env->cpsr >> 28;

	  for (;;)
	    {
	      uint32_t opcode = *pc++;
	      uint32_t cond = (opcode & 0xf0000000) >> 28;
	      if (cond == 0x0e
		  || ((cond_table[cond] >> cpsr) & 1))
		{
		  if (! ((opcode & 0x0c000000) == 0x0c000000
			 || (opcode & 0x0d000000) == 0x0d000000
			 || (opcode & 0x0e000000) == 0x0e000000
			 || (opcode & 0x0e000010) == 0x0e000010))
		    break;

		  EmulateAll (opcode, qfpa, env->regs);

		  // A CMF and CNF may have updated the CPSR.
		  cpsr = env->cpsr >> 28;
		}

	      env->regs[15] += 4;
	    }
	  handled = true;
	}
#endif

      if (trapnr == EXCP_SWI)
	{
	  unsigned int insn = ldl((void *)(env->regs[15] - 4));

	  // Some RISC OS SWIs, such as OS_Module have a requirement
	  // to call user code, and then continue executing.  We need
	  // some way of emulating some ARM code, then returning to
	  // the same point in the SWI to continue with whatever actions
	  // are necessary.  An SWI 0xdeadbe will be specially recognised
	  // and allow us to return.
	  if (insn == 0xefdeadbe)
	    return;


	  if (riscos->SWI (env, insn))
	    handled = true;
	}

#if 0
      if (trapnr == EXCP_PREFETCH_ABORT || trapnr == EXCP_DATA_ABORT)
	{
	  // Handle CPU faults in addressable space to read only memory
	  target_siginfo_t info;
	  info.si_signo = SIGSEGV;
	  info.si_errno = 0;
	  /* XXX: check env->error_code */
	  info.si_code = TARGET_SEGV_MAPERR;
	  info._sifields._sigfault._addr = env->cp15_6;
	  queue_signal(info.si_signo, &info);
	  process_pending_signals (env);
	  handled = true;
	}
#endif

      if (trapnr == EXCP_DATA_ABORT)
	{
	  uint32_t *zp = (uint32_t *) memc->memory_map[MEMC_CHUNK_ZERO].base;
	  // Check for a read/write operation to a zero-page address
	  // and re-map to the zero-page area.

	  // R15 points at the erroneous instruction.
	  unsigned int insn = ldl ((void *) env->regs[15]);

	  if (insn == 0xe58eeff0)
	    {
	      printf ("here\n");
	      zp[4080 >> 2] = env->regs[14];
	      handled = true;
	      env->regs[15] += 4;
	    }

	  if (insn == 0xe58eefec)
	    {
	      printf ("here1\n");
	      zp[4076 >> 2] = env->regs[14];
	      handled = true;
	      env->regs[15] += 4;
	    }
	}

      if (! handled)
	{
	  // Deal with any unhandled exceptions by checking the
	  // program environment and calling the relevant routine.
	  uint32_t r[4];

	  // Preserve regs from being corrupted by SWI
	  for (int i = 0; i < 4; i++)
	    r[i] = env->regs[i];

	  // Find out where to dump registers
	  env->regs[0] = 13;
	  env->regs[1] = 0;
	  riscos->SWI (env, OS_ChangeEnvironment);

	  uint32_t *buf = (uint32_t *) env->regs[1];

	  // Get address of relevant handler
	  if (trapnr == EXCP_UDEF)
	    env->regs[0] = 1; // UndefinedInstruction handler
	  else if (trapnr == EXCP_PREFETCH_ABORT)
	    env->regs[0] = 2; // PrefetchAbort handler
	  else if (trapnr == EXCP_DATA_ABORT)
	    env->regs[0] = 3; // DataAbort handler
	  else if (trapnr == EXCP_SWI)
	    env->regs[0] = 12; // UnusedSWI handler
	  else
	    env->regs[0] = 4; // OtherExceptions

	  env->regs[1] = 0;
	  riscos->SWI (env, OS_ChangeEnvironment);

	  uint32_t handler = env->regs[1];

	  // Restore regs
	  for (int i = 0; i < 4; i++)
	    env->regs[i] = r[i];

	  // Dump regs to exception buffer.
	  for (int i = 0; i < 16; i++)
	    buf[i] = env->regs[i];

	  if (handler == 0xdeadbeef || exception == true)
	    {
	      // Default handler
	      printf ("RISC OS exception %d occurred.\nRegs:\n", trapnr);
	      for (int i = 0; i < 4; i++)
		{
		  for (int j = 0; j < 4; j++)
		    printf ("R%02d=%08x ", (i * 4) + j,
			    env->regs[(i * 4) + j]);
		  printf ("\n");
		}

	      // CPU abort, throw bad exit status.
	      throw (1);
	    }
	  else
	    {
	      printf ("Unhandled exception. Jumping to %08x (pc=%08x)\n",
		      handler, env->regs[15]);
	      for (int i = 0; i < 4; i++)
		{
		  for (int j = 0; j < 4; j++)
		    printf ("R%02d=%08x ", (i * 4) + j,
			    env->regs[(i * 4) + j]);
		  printf ("\n");
		}
	      write_backtrace (env);
	      // Application has installed it's own handler.  Jump to it.
	      env->regs[14] = env->regs[15];
	      env->regs[15] = handler;
	      exception = true;
	    }
	}
    }
}

static void redirect (int infd, int outfd)
{
  const int bufsiz = 1024;
  char buf[bufsiz + 1];
  ssize_t s;

  while ((s = read (infd, buf, bufsiz)) > 0)
    write (outfd, buf, s);
}

// Redirect a file descriptor to a file
static void redirect (const char *fname, int outfd)
{
  int fd = open (fname, O_RDONLY | O_EXCL);
  redirect (fd, outfd);
  close (fd);
}

int main (int argc, char **argv)
{
  int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1;
  char stdin_redir[20], stdout_redir[20], stderr_redir[20];

  // If set, then QEMU's internal debugging will be output to a file
  // in /tmp.  This isn't really useful unless you want to look at
  // code generation.
  //loglevel = 1;

  if (argc <= 1)
    {
      printf ("qemu-arm (RISC OS)\n");
      return 1;
    }

#if 0
  // If RISCOS_NOFILETYPE is defined, then we will not check for
  // or encode additional information in the filename for filetypes
  filetype_handling = true;
  char *s = getenv ("RISCOS_NOFILETYPE");
  if (s)
    filetype_handling = false;
#endif

  // Currently force off until an environment that can work with
  // RISC OS's mangled filenames can be built.
  filetype_handling = false;

  /* Check whether stdin, stdout and stderr point to a tty.  If they do
     not, then we can assume that the shell has redirected them.

     The problem we face is that the RISC OS application assumes that it
     is the primary application, whereby in reality it is a sub-process
     of this emulator.

     If a command is executed like:
        $ hello > foo

     Then the Unix shell will setup setup FD1 to write to 'foo' before
     calling the emulator.

     In the RISC OS world, we do not have the benefit of such a feature
     so UnixLib does the requisite open('foo') calls.

     To work around this, we create temporary files that UnixLib can
     work upon and on program exit redirect to the proper file descriptors
     for the Unix shell.  */
  struct stat sb0, sb1, sb2;
  fstat (0, &sb0);
  fstat (1, &sb1);
  fstat (2, &sb2);

  *stdin_redir = '\0';
  *stdout_redir = '\0';
  *stderr_redir = '\0';
  if (! S_ISCHR (sb0.st_mode))
    {
      strcpy (stdin_redir, "/tmp/ro0XXXXXX");
      stdin_fd = mkstemp (stdin_redir);
      
      redirect (0, stdin_fd);
      close (stdin_fd);
    }

  if (! S_ISCHR (sb1.st_mode))
    {
      strcpy (stdout_redir, "/tmp/ro1XXXXXX");
      stdout_fd = mkstemp (stdout_redir);
      close (stdout_fd);
    }

  // FIXME.  This comparison isn't perfect and on occasion stdout
  // and stderr appear as duplicates when they shouldn't.
  if (sb1.st_dev == sb2.st_dev
      || sb1.st_ino == sb2.st_ino)
    {
      // stderr is a duplicate of stdout
      strcpy (stderr_redir, "!dup1");
    }
  else
    {
      if (! S_ISCHR (sb2.st_mode))
	{
	  strcpy (stderr_redir, "/tmp/ro2XXXXXX");
	  stderr_fd = mkstemp (stderr_redir);
	}
    }

  // init debug
  cpu_set_log_filename(DEBUG_LOGFILE);

  if (loglevel)
    {
      // For QEMU debugging, this means we're interested in the
      // input and output assembler (i.e. before and after translation)
      // and CPU states.
      int mask = cpu_str_to_log_mask ("in_asm,out_asm,op");
      cpu_set_log (mask);
    }

  // Initialise CPU structures and clear registers
  CPUState *env = cpu_init ();
  signal_init ();
  global_env = env;

  env->user_mode_only = 1;
  for (int i = 0; i < 16; i++)
    env->regs[i] = 0;
  env->cpsr = 0x10;
  
  // Build Task State
  TaskState ts;
  memset (&ts, 0, sizeof (TaskState));
  env->opaque = &ts;
  ts.used = 1;

  // Initialise the RISC OS memory controller
  memc = new MEMC (0);

  // The operating system emulation class.
  // Initialise the RISC OS world.
  riscos = new RISCOS ();

  // Load any required RISC OS native modules
  riscos->LoadModules (env);

  // Loads image, sets command args and initialises program counter
  riscos->Exec (env, argc, argv,
		stdin_redir, stdout_redir, stderr_redir);

  int exit_code = 0;
  try
    {
      // Enter a CPU emulation infinite loop.
      cpu_loop(env);
    }

  // Here we are catching an exception thrown by SWI OS_Exit when
  // called with R1=ABEX.
  catch (int return_code)
    {
      // Pass the return code of the RISC OS application
      // back to the UNIX environment.  Should allow
      // failed applications to stop parent processes
      // like make from continuing.
      exit_code = return_code;
    }

  if (stdout_fd != -1)
    {
      redirect (stdout_redir, 1);
      unlink (stdout_redir);
    }

  if (stderr_fd != -1)
    {
      redirect (stderr_redir, 2);
      unlink (stderr_redir);
    }

  if (stdin_fd != -1)
    unlink (stdin_redir);

  if (loglevel)
    dump_exec_info (logfile, fprintf);

  return exit_code;
}
