/* Copyright 2007-2015 Free Software Foundation, Inc.

   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/>.

   This file is part of the gdb testsuite.

   Contributed by Markus Deuling <deuling@de.ibm.com>.
   Tests for 'info spu' commands.  */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <spu_mfcio.h>


/* PPE-assisted call interface.  */
void
send_to_ppe (unsigned int signalcode, unsigned int opcode, void *data)
{
  __vector unsigned int stopfunc =
    {
      signalcode,     /* stop */
      (opcode << 24) | (unsigned int) data,
      0x4020007f,     /* nop */
      0x35000000      /* bi $0 */
    };

  void (*f) (void) = (void *) &stopfunc;
  asm ("sync");
  f ();
}

/* PPE-assisted call to mmap from SPU.  */
unsigned long long
mmap_ea (unsigned long long start, size_t length,
         int prot, int flags, int fd, off_t offset)
{
  struct mmap_args
    {
      unsigned long long start __attribute__ ((aligned (16)));
      size_t length __attribute__ ((aligned (16)));
      int prot __attribute__ ((aligned (16)));
      int flags __attribute__ ((aligned (16)));
      int fd __attribute__ ((aligned (16)));
      off_t offset __attribute__ ((aligned (16)));
    } args;

  args.start = start;
  args.length = length;
  args.prot = prot;
  args.flags = flags;
  args.fd = fd;
  args.offset = offset;

  send_to_ppe (0x2101, 11, &args);
  return args.start;
}

/* This works only in a Linux environment with <= 1024 open
   file descriptors for one process. Result is the file
   descriptor for the current context if available.  */
int
find_context_fd (void)
{
  int dir_fd = -1;
  int i;

  for (i = 0; i < 1024; i++)
    {
      struct stat stat;

      if (fstat (i, &stat) < 0)
        break;
      if (S_ISDIR (stat.st_mode))
        dir_fd = dir_fd == -1 ? i : -2;
    }
  return dir_fd < 0 ? -1 : dir_fd;
}

/* Open the context file and return the file handler.  */
int
open_context_file (int context_fd, char *name, int flags)
{
  char buf[128];

  if (context_fd < 0)
    return -1;

  sprintf (buf, "/proc/self/fd/%d/%s", context_fd, name);
  return open (buf, flags);
}


int
do_event_test ()
{
  spu_write_event_mask (MFC_MULTI_SRC_SYNC_EVENT); /* 0x1000 */  /* Marker Event */
  spu_write_event_mask (MFC_PRIV_ATTN_EVENT); /* 0x0800 */
  spu_write_event_mask (MFC_LLR_LOST_EVENT); /* 0x0400 */
  spu_write_event_mask (MFC_SIGNAL_NOTIFY_1_EVENT); /* 0x0200 */
  spu_write_event_mask (MFC_SIGNAL_NOTIFY_2_EVENT); /* 0x0100 */
  spu_write_event_mask (MFC_OUT_MBOX_AVAILABLE_EVENT); /* 0x0080 */
  spu_write_event_mask (MFC_OUT_INTR_MBOX_AVAILABLE_EVENT); /* 0x0040 */
  spu_write_event_mask (MFC_DECREMENTER_EVENT); /* 0x0020 */
  spu_write_event_mask (MFC_IN_MBOX_AVAILABLE_EVENT); /* 0x0010 */
  spu_write_event_mask (MFC_COMMAND_QUEUE_AVAILABLE_EVENT); /* 0x0008 */
  spu_write_event_mask (MFC_LIST_STALL_NOTIFY_EVENT); /* 0x0002 */
  spu_write_event_mask (MFC_TAG_STATUS_UPDATE_EVENT); /* 0x0001 */

  return 0;
}

int
do_dma_test ()
{
  #define MAP_FAILED      (-1ULL)
  #define PROT_READ       0x1
  #define MAP_PRIVATE     0x002
  #define BSIZE 128
  static char buf[BSIZE] __attribute__ ((aligned (128)));
  char *file = "/var/tmp/tmp_buf";
  struct stat fdstat;
  int fd, cnt;
  unsigned long long src;

  /* Create a file and fill it with some bytes.  */
  fd = open (file, O_CREAT | O_RDWR | O_TRUNC, 0777);
  if (fd == -1)
    return -1;
  memset ((void *)buf, '1', BSIZE);
  write (fd, buf, BSIZE);
  write (fd, buf, BSIZE);
  memset ((void *)buf, 0, BSIZE);

  if (fstat (fd, &fdstat) != 0
      || !fdstat.st_size)
    return -2;

  src = mmap_ea(0ULL, fdstat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (src == MAP_FAILED)
    return -3;

  /* Copy some data via DMA.  */
  mfc_get (&buf, src, BSIZE, 5, 0, 0);   /* Marker DMA */
  mfc_write_tag_mask (1<<5);   /* Marker DMAWait */
  spu_mfcstat (MFC_TAG_UPDATE_ALL);

  /* Close the file.  */
  close (fd);

  return cnt;
}

int
do_mailbox_test ()
{
  /* Write to SPU Outbound Mailbox.  */
  if (spu_stat_out_mbox ())            /* Marker Mbox */
    spu_write_out_mbox (0x12345678);

  /* Write to SPU Outbound Interrupt Mailbox.  */
  if (spu_stat_out_intr_mbox ())
    spu_write_out_intr_mbox (0x12345678);

  return 0;       /* Marker MboxEnd */
}

int
do_signal_test ()
{
  struct stat fdstat;
  int context_fd = find_context_fd ();
  int ret, buf, fd;

  buf = 23;    /* Marker Signal */
  /* Write to signal1.  */
  fd = open_context_file (context_fd, "signal1", O_RDWR);
  if (fstat (fd, &fdstat) != 0)
    return -1;
  ret = write (fd, buf, sizeof (int));
  close (fd);  /* Marker Signal1 */

  /* Write to signal2.  */
  fd = open_context_file (context_fd, "signal2", O_RDWR);
  if (fstat (fd, &fdstat) != 0)
    return -1;
  ret = write (fd, buf, sizeof (int));
  close (fd);  /* Marker Signal2 */

  /* Read signal1.  */
  if (spu_stat_signal1 ())
    ret = spu_read_signal1 ();

  /* Read signal2.  */
  if (spu_stat_signal2 ())
    ret = spu_read_signal2 ();   /* Marker SignalRead */

  return 0;
}

int
main (unsigned long long speid, unsigned long long argp, 
      unsigned long long envp)
{
  int res;

  /* info spu event  */
  res = do_event_test ();

  /* info spu dma  */
  res = do_dma_test ();

  /* info spu mailbox  */
  res = do_mailbox_test ();

  /* info spu signal  */
  res = do_signal_test ();

  return 0;
}

