/* syscalls.c --- implement system calls for the M32C simulator.

Copyright (C) 2005-2015 Free Software Foundation, Inc.
Contributed by Red Hat, Inc.

This file is part of the GNU simulators.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

#include "gdb/callback.h"

#include "cpu.h"
#include "mem.h"
#include "syscalls.h"

#include "syscall.h"

/* The current syscall callbacks we're using.  */
static struct host_callback_struct *callbacks;

void
set_callbacks (struct host_callback_struct *cb)
{
  callbacks = cb;
}


/* A16 ABI: arg1 in r1l (QI) or r1 (HI) or stack
            arg2 in r2 (HI) or stack
	    arg3..N on stack
	    padding: none

   A24 ABI: arg1 in r0l (QI) or r0 (HI) or stack
	    arg2..N on stack
	    padding: qi->hi

   return value in r0l (QI) r0 (HI) r2r0 (SI)
     structs: pointer pushed on stack last

*/

int argp, stackp;

static int
arg (int bytes)
{
  int rv = 0;
  argp++;
  if (A16)
    {
      switch (argp)
	{
	case 1:
	  if (bytes == 1)
	    return get_reg (r1l);
	  if (bytes == 2)
	    return get_reg (r1);
	  break;
	case 2:
	  if (bytes == 2)
	    return get_reg (r2);
	  break;
	}
    }
  else
    {
      switch (argp)
	{
	case 1:
	  if (bytes == 1)
	    return get_reg (r0l);
	  if (bytes == 2)
	    return get_reg (r0);
	  break;
	}
    }
  if (bytes == 0)
    bytes = 2;
  switch (bytes)
    {
    case 1:
      rv = mem_get_qi (get_reg (sp) + stackp);
      if (A24)
	stackp++;
      break;
    case 2:
      rv = mem_get_hi (get_reg (sp) + stackp);
      break;
    case 3:
      rv = mem_get_psi (get_reg (sp) + stackp);
      if (A24)
	stackp++;
      break;
    case 4:
      rv = mem_get_si (get_reg (sp) + stackp);
      break;
    }
  stackp += bytes;
  return rv;
}

static void
read_target (char *buffer, int address, int count, int asciiz)
{
  char byte;
  while (count > 0)
    {
      byte = mem_get_qi (address++);
      *buffer++ = byte;
      if (asciiz && (byte == 0))
	return;
      count--;
    }
}

static void
write_target (char *buffer, int address, int count, int asciiz)
{
  char byte;
  while (count > 0)
    {
      byte = *buffer++;
      mem_put_qi (address++, byte);
      if (asciiz && (byte == 0))
	return;
      count--;
    }
}

#define PTRSZ (A16 ? 2 : 3)

static char *callnames[] = {
  "SYS_zero",
  "SYS_exit",
  "SYS_open",
  "SYS_close",
  "SYS_read",
  "SYS_write",
  "SYS_lseek",
  "SYS_unlink",
  "SYS_getpid",
  "SYS_kill",
  "SYS_fstat",
  "SYS_sbrk",
  "SYS_argvlen",
  "SYS_argv",
  "SYS_chdir",
  "SYS_stat",
  "SYS_chmod",
  "SYS_utime",
  "SYS_time",
  "SYS_gettimeofday",
  "SYS_times",
  "SYS_link"
};

void
m32c_syscall (int id)
{
  static char buf[256];
  int rv;

  argp = 0;
  stackp = A16 ? 3 : 4;
  if (trace)
    printf ("\033[31m/* SYSCALL(%d) = %s */\033[0m\n", id, callnames[id]);
  switch (id)
    {
    case SYS_exit:
      {
	int ec = arg (2);
	if (verbose)
	  printf ("[exit %d]\n", ec);
	step_result = M32C_MAKE_EXITED (ec);
      }
      break;

    case SYS_open:
      {
	int path = arg (PTRSZ);
	int oflags = arg (2);
	int cflags = arg (2);

	read_target (buf, path, 256, 1);
	if (trace)
	  printf ("open(\"%s\",0x%x,%#o) = ", buf, oflags, cflags);

	if (callbacks)
	  /* The callback vector ignores CFLAGS.  */
	  rv = callbacks->open (callbacks, buf, oflags);
	else
	  {
	    int h_oflags = 0;

	    if (oflags & 0x0001)
	      h_oflags |= O_WRONLY;
	    if (oflags & 0x0002)
	      h_oflags |= O_RDWR;
	    if (oflags & 0x0200)
	      h_oflags |= O_CREAT;
	    if (oflags & 0x0008)
	      h_oflags |= O_APPEND;
	    if (oflags & 0x0400)
	      h_oflags |= O_TRUNC;
	    rv = open (buf, h_oflags, cflags);
	  }
	if (trace)
	  printf ("%d\n", rv);
	put_reg (r0, rv);
      }
      break;

    case SYS_close:
      {
	int fd = arg (2);

	if (callbacks)
	  rv = callbacks->close (callbacks, fd);
	else if (fd > 2)
	  rv = close (fd);
	else
	  rv = 0;
	if (trace)
	  printf ("close(%d) = %d\n", fd, rv);
	put_reg (r0, rv);
      }
      break;

    case SYS_read:
      {
	int fd = arg (2);
	int addr = arg (PTRSZ);
	int count = arg (2);

	if (count > sizeof (buf))
	  count = sizeof (buf);
	if (callbacks)
	  rv = callbacks->read (callbacks, fd, buf, count);
	else
	  rv = read (fd, buf, count);
	if (trace)
	  printf ("read(%d,%d) = %d\n", fd, count, rv);
	if (rv > 0)
	  write_target (buf, addr, rv, 0);
	put_reg (r0, rv);
      }
      break;

    case SYS_write:
      {
	int fd = arg (2);
	int addr = arg (PTRSZ);
	int count = arg (2);

	if (count > sizeof (buf))
	  count = sizeof (buf);
	if (trace)
	  printf ("write(%d,0x%x,%d)\n", fd, addr, count);
	read_target (buf, addr, count, 0);
	if (trace)
	  fflush (stdout);
	if (callbacks)
	  rv = callbacks->write (callbacks, fd, buf, count);
	else
	  rv = write (fd, buf, count);
	if (trace)
	  printf ("write(%d,%d) = %d\n", fd, count, rv);
	put_reg (r0, rv);
      }
      break;

    case SYS_getpid:
      put_reg (r0, 42);
      break;

    case SYS_gettimeofday:
      {
	int tvaddr = arg (PTRSZ);
	struct timeval tv;

	rv = gettimeofday (&tv, 0);
	if (trace)
	  printf ("gettimeofday: %ld sec %ld usec to 0x%x\n", tv.tv_sec,
		  tv.tv_usec, tvaddr);
	mem_put_si (tvaddr, tv.tv_sec);
	mem_put_si (tvaddr + 4, tv.tv_usec);
	put_reg (r0, rv);
      }
      break;

    case SYS_kill:
      {
	int pid = arg (2);
	int sig = arg (2);
	if (pid == 42)
	  {
	    if (verbose)
	      printf ("[signal %d]\n", sig);
	    step_result = M32C_MAKE_STOPPED (sig);
	  }
      }
      break;

    case 11:
      {
	int heaptop_arg = arg (PTRSZ);
	if (trace)
	  printf ("sbrk: heap top set to %x\n", heaptop_arg);
	heaptop = heaptop_arg;
	if (heapbottom == 0)
	  heapbottom = heaptop_arg;
      }
      break;

    }
}
