/* SPU native-dependent code for GDB, the GNU debugger.
   Copyright (C) 2006 Free Software Foundation, Inc.

   Contributed by Ulrich Weigand <uweigand@de.ibm.com>.

   This file is part of GDB.

   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 2 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, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.  */

#include "defs.h"
#include "gdbcore.h"
#include "gdb_string.h"
#include "target.h"
#include "inferior.h"
#include "inf-ptrace.h"
#include "regcache.h"
#include "symfile.h"
#include "gdb_wait.h"

#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <sys/types.h>
#include <sys/param.h>

#include "spu-tdep.h"

/* PPU side system calls.  */
#define INSTR_SC	0x44000002
#define NR_spu_run	0x0116


/* Fetch PPU register REGNO.  */
static CORE_ADDR
fetch_ppc_register (int regno)
{
  PTRACE_TYPE_RET res;

  int tid = TIDGET (inferior_ptid);
  if (tid == 0)
    tid = PIDGET (inferior_ptid);

#ifndef __powerpc64__
  /* If running as a 32-bit process on a 64-bit system, we attempt
     to get the full 64-bit register content of the target process.
     If the PPC special ptrace call fails, we're on a 32-bit system;
     just fall through to the regular ptrace call in that case.  */
  {
    gdb_byte buf[8];

    errno = 0;
    ptrace (PPC_PTRACE_PEEKUSR_3264, tid,
	    (PTRACE_TYPE_ARG3) (regno * 8), buf);
    if (errno == 0)
      ptrace (PPC_PTRACE_PEEKUSR_3264, tid,
	      (PTRACE_TYPE_ARG3) (regno * 8 + 4), buf + 4);
    if (errno == 0)
      return (CORE_ADDR) *(unsigned long long *)buf;
  }
#endif

  errno = 0;
  res = ptrace (PT_READ_U, tid,
	 	(PTRACE_TYPE_ARG3) (regno * sizeof (PTRACE_TYPE_RET)), 0);
  if (errno != 0)
    {
      char mess[128];
      xsnprintf (mess, sizeof mess, "reading PPC register #%d", regno);
      perror_with_name (_(mess));
    }

  return (CORE_ADDR) (unsigned long) res;
}

/* Fetch WORD from PPU memory at (aligned) MEMADDR in thread TID.  */
static int
fetch_ppc_memory_1 (int tid, CORE_ADDR memaddr, PTRACE_TYPE_RET *word)
{
  errno = 0;

#ifndef __powerpc64__
  if (memaddr >> 32)
    {
      unsigned long long addr_8 = (unsigned long long) memaddr;
      ptrace (PPC_PTRACE_PEEKTEXT_3264, tid, (PTRACE_TYPE_ARG3) &addr_8, word);
    }
  else
#endif
    *word = ptrace (PT_READ_I, tid, (PTRACE_TYPE_ARG3) (size_t) memaddr, 0);

  return errno;
}

/* Store WORD into PPU memory at (aligned) MEMADDR in thread TID.  */
static int
store_ppc_memory_1 (int tid, CORE_ADDR memaddr, PTRACE_TYPE_RET word)
{
  errno = 0;

#ifndef __powerpc64__
  if (memaddr >> 32)
    {
      unsigned long long addr_8 = (unsigned long long) memaddr;
      ptrace (PPC_PTRACE_POKEDATA_3264, tid, (PTRACE_TYPE_ARG3) &addr_8, word);
    }
  else
#endif
    ptrace (PT_WRITE_D, tid, (PTRACE_TYPE_ARG3) (size_t) memaddr, word);

  return errno;
}

/* Fetch LEN bytes of PPU memory at MEMADDR to MYADDR.  */
static int
fetch_ppc_memory (CORE_ADDR memaddr, gdb_byte *myaddr, int len)
{
  int i, ret;

  CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_TYPE_RET);
  int count = ((((memaddr + len) - addr) + sizeof (PTRACE_TYPE_RET) - 1)
	       / sizeof (PTRACE_TYPE_RET));
  PTRACE_TYPE_RET *buffer;

  int tid = TIDGET (inferior_ptid);
  if (tid == 0)
    tid = PIDGET (inferior_ptid);

  buffer = (PTRACE_TYPE_RET *) alloca (count * sizeof (PTRACE_TYPE_RET));
  for (i = 0; i < count; i++, addr += sizeof (PTRACE_TYPE_RET))
    if ((ret = fetch_ppc_memory_1 (tid, addr, &buffer[i])) != 0)
      return ret;

  memcpy (myaddr,
	  (char *) buffer + (memaddr & (sizeof (PTRACE_TYPE_RET) - 1)),
	  len);

  return 0;
}

/* Store LEN bytes from MYADDR to PPU memory at MEMADDR.  */
static int
store_ppc_memory (CORE_ADDR memaddr, const gdb_byte *myaddr, int len)
{
  int i, ret;

  CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_TYPE_RET);
  int count = ((((memaddr + len) - addr) + sizeof (PTRACE_TYPE_RET) - 1)
	       / sizeof (PTRACE_TYPE_RET));
  PTRACE_TYPE_RET *buffer;

  int tid = TIDGET (inferior_ptid);
  if (tid == 0)
    tid = PIDGET (inferior_ptid);

  buffer = (PTRACE_TYPE_RET *) alloca (count * sizeof (PTRACE_TYPE_RET));

  if (addr != memaddr || len < (int) sizeof (PTRACE_TYPE_RET))
    if ((ret = fetch_ppc_memory_1 (tid, addr, &buffer[0])) != 0)
      return ret;

  if (count > 1)
    if ((ret = fetch_ppc_memory_1 (tid, addr + (count - 1)
					       * sizeof (PTRACE_TYPE_RET),
				   &buffer[count - 1])) != 0)
      return ret;

  memcpy ((char *) buffer + (memaddr & (sizeof (PTRACE_TYPE_RET) - 1)),
          myaddr, len);

  for (i = 0; i < count; i++, addr += sizeof (PTRACE_TYPE_RET))
    if ((ret = store_ppc_memory_1 (tid, addr, buffer[i])) != 0)
      return ret;

  return 0;
}


/* If the PPU thread is currently stopped on a spu_run system call,
   return to FD and ADDR the file handle and NPC parameter address
   used with the system call.  Return non-zero if successful.  */
static int 
parse_spufs_run (int *fd, CORE_ADDR *addr)
{
  gdb_byte buf[4];
  CORE_ADDR pc = fetch_ppc_register (32);  /* nip */

  /* Fetch instruction preceding current NIP.  */
  if (fetch_ppc_memory (pc-4, buf, 4) != 0)
    return 0;
  /* It should be a "sc" instruction.  */
  if (extract_unsigned_integer (buf, 4) != INSTR_SC)
    return 0;
  /* System call number should be NR_spu_run.  */
  if (fetch_ppc_register (0) != NR_spu_run)
    return 0;

  /* Register 3 contains fd, register 4 the NPC param pointer.  */
  *fd = fetch_ppc_register (34);  /* orig_gpr3 */
  *addr = fetch_ppc_register (4);
  return 1;
}


/* Copy LEN bytes at OFFSET in spufs file ANNEX into/from READBUF or WRITEBUF,
   using the /proc file system.  */
static LONGEST
spu_proc_xfer_spu (const char *annex, gdb_byte *readbuf,
		   const gdb_byte *writebuf,
		   ULONGEST offset, LONGEST len)
{
  char buf[128];
  int fd = 0;
  int ret = -1;
  int pid = PIDGET (inferior_ptid);

  if (!annex)
    return 0;

  xsnprintf (buf, sizeof buf, "/proc/%d/fd/%s", pid, annex);
  fd = open (buf, writebuf? O_WRONLY : O_RDONLY);
  if (fd <= 0)
    return -1;

  if (offset != 0
      && lseek (fd, (off_t) offset, SEEK_SET) != (off_t) offset)
    {
      close (fd);
      return -1;
    }

  if (writebuf)
    ret = write (fd, writebuf, (size_t) len);
  else if (readbuf)
    ret = read (fd, readbuf, (size_t) len);

  close (fd);
  return ret;
}


/* Inferior memory should contain an SPE executable image at location ADDR.
   Allocate a BFD representing that executable.  Return NULL on error.  */

static void *
spu_bfd_iovec_open (struct bfd *nbfd, void *open_closure)
{
  return open_closure;
}

static int
spu_bfd_iovec_close (struct bfd *nbfd, void *stream)
{
  xfree (stream);
  return 1;
}

static file_ptr
spu_bfd_iovec_pread (struct bfd *abfd, void *stream, void *buf,
	             file_ptr nbytes, file_ptr offset)
{
  CORE_ADDR addr = *(CORE_ADDR *)stream;

  if (fetch_ppc_memory (addr + offset, buf, nbytes) != 0)
    {
      bfd_set_error (bfd_error_invalid_operation);
      return -1;
    }

  return nbytes;
}

static bfd *
spu_bfd_open (CORE_ADDR addr)
{
  struct bfd *nbfd;

  CORE_ADDR *open_closure = xmalloc (sizeof (CORE_ADDR));
  *open_closure = addr;

  nbfd = bfd_openr_iovec (xstrdup ("<in-memory>"), "elf32-spu",
			  spu_bfd_iovec_open, open_closure,
			  spu_bfd_iovec_pread, spu_bfd_iovec_close);
  if (!nbfd)
    return NULL;

  if (!bfd_check_format (nbfd, bfd_object))
    {
      bfd_close (nbfd);
      return NULL;
    }

  return nbfd;
}

/* INFERIOR_FD is a file handle passed by the inferior to the
   spu_run system call.  Assuming the SPE context was allocated
   by the libspe library, try to retrieve the main SPE executable
   file from its copy within the target process.  */
static void
spu_symbol_file_add_from_memory (int inferior_fd)
{
  CORE_ADDR addr;
  struct bfd *nbfd;

  char id[128];
  char annex[32];
  int len;

  /* Read object ID.  */
  xsnprintf (annex, sizeof annex, "%d/object-id", inferior_fd);
  len = spu_proc_xfer_spu (annex, id, NULL, 0, sizeof id);
  if (len <= 0 || len >= sizeof id)
    return;
  id[len] = 0;
  if (sscanf (id, "0x%llx", &addr) != 1)
    return;

  /* Open BFD representing SPE executable and read its symbols.  */
  nbfd = spu_bfd_open (addr);
  if (nbfd)
    symbol_file_add_from_bfd (nbfd, 0, NULL, 1, 0);
}


/* Override the post_startup_inferior routine to continue running
   the inferior until the first spu_run system call.  */
static void
spu_child_post_startup_inferior (ptid_t ptid)
{
  int fd;
  CORE_ADDR addr;

  int tid = TIDGET (ptid);
  if (tid == 0)
    tid = PIDGET (ptid);
  
  while (!parse_spufs_run (&fd, &addr))
    {
      ptrace (PT_SYSCALL, tid, (PTRACE_TYPE_ARG3) 0, 0);
      waitpid (tid, NULL, __WALL | __WNOTHREAD);
    }
}

/* Override the post_attach routine to try load the SPE executable
   file image from its copy inside the target process.  */
static void
spu_child_post_attach (int pid)
{
  int fd;
  CORE_ADDR addr;

  /* Like child_post_startup_inferior, if we happened to attach to
     the inferior while it wasn't currently in spu_run, continue 
     running it until we get back there.  */
  while (!parse_spufs_run (&fd, &addr))
    {
      ptrace (PT_SYSCALL, pid, (PTRACE_TYPE_ARG3) 0, 0);
      waitpid (pid, NULL, __WALL | __WNOTHREAD);
    }

  /* If the user has not provided an executable file, try to extract
     the image from inside the target process.  */
  if (!get_exec_file (0))
    spu_symbol_file_add_from_memory (fd);
}

/* Wait for child PTID to do something.  Return id of the child,
   minus_one_ptid in case of error; store status into *OURSTATUS.  */
static ptid_t
spu_child_wait (ptid_t ptid, struct target_waitstatus *ourstatus)
{
  int save_errno;
  int status;
  pid_t pid;

  do
    {
      set_sigint_trap ();	/* Causes SIGINT to be passed on to the
				   attached process.  */
      set_sigio_trap ();

      pid = waitpid (PIDGET (ptid), &status, 0);
      if (pid == -1 && errno == ECHILD)
	/* Try again with __WCLONE to check cloned processes.  */
	pid = waitpid (PIDGET (ptid), &status, __WCLONE);

      save_errno = errno;

      /* Make sure we don't report an event for the exit of the
         original program, if we've detached from it.  */
      if (pid != -1 && !WIFSTOPPED (status) && pid != PIDGET (inferior_ptid))
	{
	  pid = -1;
	  save_errno = EINTR;
	}

      clear_sigio_trap ();
      clear_sigint_trap ();
    }
  while (pid == -1 && save_errno == EINTR);

  if (pid == -1)
    {
      warning ("Child process unexpectedly missing: %s",
	       safe_strerror (save_errno));

      /* Claim it exited with unknown signal.  */
      ourstatus->kind = TARGET_WAITKIND_SIGNALLED;
      ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN;
      return minus_one_ptid;
    }

  store_waitstatus (ourstatus, status);
  return pid_to_ptid (pid);
}

/* Override the fetch_inferior_register routine.  */
static void
spu_fetch_inferior_registers (int regno)
{
  int fd;
  CORE_ADDR addr;

  /* We must be stopped on a spu_run system call.  */
  if (!parse_spufs_run (&fd, &addr))
    return;

  /* The ID register holds the spufs file handle.  */
  if (regno == -1 || regno == SPU_ID_REGNUM)
    {
      char buf[4];
      store_unsigned_integer (buf, 4, fd);
      regcache_raw_supply (current_regcache, SPU_ID_REGNUM, buf);
    }

  /* The NPC register is found at ADDR.  */
  if (regno == -1 || regno == SPU_PC_REGNUM)
    {
      gdb_byte buf[4];
      if (fetch_ppc_memory (addr, buf, 4) == 0)
	regcache_raw_supply (current_regcache, SPU_PC_REGNUM, buf);
    }

  /* The GPRs are found in the "regs" spufs file.  */
  if (regno == -1 || (regno >= 0 && regno < SPU_NUM_GPRS))
    {
      gdb_byte buf[16 * SPU_NUM_GPRS];
      char annex[32];
      int i;

      xsnprintf (annex, sizeof annex, "%d/regs", fd);
      if (spu_proc_xfer_spu (annex, buf, NULL, 0, sizeof buf) == sizeof buf)
	for (i = 0; i < SPU_NUM_GPRS; i++)
	  regcache_raw_supply (current_regcache, i, buf + i*16);
    }
}

/* Override the store_inferior_register routine.  */
static void
spu_store_inferior_registers (int regno)
{
  int fd;
  CORE_ADDR addr;

  /* We must be stopped on a spu_run system call.  */
  if (!parse_spufs_run (&fd, &addr))
    return;

  /* The NPC register is found at ADDR.  */
  if (regno == -1 || regno == SPU_PC_REGNUM)
    {
      gdb_byte buf[4];
      regcache_raw_collect (current_regcache, SPU_PC_REGNUM, buf);
      store_ppc_memory (addr, buf, 4);
    }

  /* The GPRs are found in the "regs" spufs file.  */
  if (regno == -1 || (regno >= 0 && regno < SPU_NUM_GPRS))
    {
      gdb_byte buf[16 * SPU_NUM_GPRS];
      char annex[32];
      int i;

      for (i = 0; i < SPU_NUM_GPRS; i++)
	regcache_raw_collect (current_regcache, i, buf + i*16);

      xsnprintf (annex, sizeof annex, "%d/regs", fd);
      spu_proc_xfer_spu (annex, NULL, buf, 0, sizeof buf);
    }
}

/* Override the to_xfer_partial routine.  */
static LONGEST 
spu_xfer_partial (struct target_ops *ops,
		  enum target_object object, const char *annex,
		  gdb_byte *readbuf, const gdb_byte *writebuf,
		  ULONGEST offset, LONGEST len)
{
  if (object == TARGET_OBJECT_MEMORY)
    {
      int fd;
      CORE_ADDR addr;
      char mem_annex[32];

      /* We must be stopped on a spu_run system call.  */
      if (!parse_spufs_run (&fd, &addr))
	return 0;

      /* Use the "mem" spufs file to access SPU local store.  */
      xsnprintf (mem_annex, sizeof mem_annex, "%d/mem", fd);
      return spu_proc_xfer_spu (mem_annex, readbuf, writebuf, offset, len);
    }

  return 0;
}

/* Override the to_can_use_hw_breakpoint routine.  */
static int
spu_can_use_hw_breakpoint (int type, int cnt, int othertype)
{
  return 0;
}


/* Initialize SPU native target.  */
void 
_initialize_spu_nat (void)
{
  /* Generic ptrace methods.  */
  struct target_ops *t;
  t = inf_ptrace_target ();

  /* Add SPU methods.  */
  t->to_post_attach = spu_child_post_attach;  
  t->to_post_startup_inferior = spu_child_post_startup_inferior;
  t->to_wait = spu_child_wait;
  t->to_fetch_registers = spu_fetch_inferior_registers;
  t->to_store_registers = spu_store_inferior_registers;
  t->to_xfer_partial = spu_xfer_partial;
  t->to_can_use_hw_breakpoint = spu_can_use_hw_breakpoint;

  /* Register SPU target.  */
  add_target (t);
}

