/* Copyright (c) 2009, 2010, 2011, 2012 ARM Ltd.  All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
 3. The name of the company may not be used to endorse or promote
    products derived from this software without specific prior written
    permission.

 THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

/* Support files for GNU libc.  Files in the system namespace go here.
   Files in the C namespace (ie those that do not start with an
   underscore) go in .c.  */

#include <_ansi.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include <errno.h>
#include <reent.h>
#include <unistd.h>
#include <sys/wait.h>
#include "svc.h"

/* Safe casting in both LP64 and ILP32.  */
#define POINTER_TO_PARAM_BLOCK_T(PTR)		\
  (param_block_t)(unsigned long) (PTR)

/* Forward prototypes.  */
int _system (const char *);
int _rename (const char *, const char *);
int _isatty (int);
clock_t _times (struct tms *);
int _gettimeofday (struct timeval *, void *);
int _unlink (const char *);
int _link (void);
int _stat (const char *, struct stat *);
int _fstat (int, struct stat *);
int _swistat (int fd, struct stat * st);
caddr_t _sbrk (int);
int _getpid (int);
int _close (int);
clock_t _clock (void);
int _swiclose (int);
int _open (const char *, int, ...);
int _swiopen (const char *, int);
int _write (int, char *, int);
int _swiwrite (int, char *, int);
int _lseek (int, int, int);
int _swilseek (int, int, int);
int _read (int, char *, int);
int _swiread (int, char *, int);
void initialise_monitor_handles (void);

static int checkerror (int);
static int error (int);
static int get_errno (void);

/* Semihosting utilities.  */
static void initialise_semihosting_exts (void);

/* Struct used to keep track of the file position, just so we
   can implement fseek(fh,x,SEEK_CUR).  */
struct fdent
{
  int handle;
  int flags;
  ino_t ino;
  int pos;
};

#define MAX_OPEN_FILES 20

/* User file descriptors (fd) are integer indexes into
   the openfiles[] array. Error checking is done by using
   findslot().

   This openfiles array is manipulated directly by only
   these 5 functions:

	findslot() - Translate entry.
	newslot() - Find empty entry.
	initilise_monitor_handles() - Initialize entries.
	_swiopen() - Initialize entry.
	_close() - Handle stdout == stderr case.

   Every other function must use findslot().  */

static struct fdent openfiles[MAX_OPEN_FILES];

static struct fdent *findslot (int);
static int newslot (void);

/* Register name faking - works in collusion with the linker.  */
#ifdef __ILP32__
register char * stack_ptr asm ("wsp");
#else
register char * stack_ptr asm ("sp");
#endif


/* following is copied from libc/stdio/local.h to check std streams */
extern void __sinit (struct _reent *);
#define CHECK_INIT(ptr) \
  do						\
    {						\
      if ((ptr) && !(ptr)->__sdidinit)		\
	__sinit (ptr);				\
    }						\
  while (0)

static int monitor_stdin;
static int monitor_stdout;
static int monitor_stderr;

static int supports_ext_exit_extended = -1;
static int supports_ext_stdout_stderr = -1;

/* Return a pointer to the structure associated with
   the user file descriptor fd. */
static struct fdent *
findslot (int fd)
{
  CHECK_INIT (_REENT);

  /* User file descriptor is out of range. */
  if ((unsigned int) fd >= MAX_OPEN_FILES)
    return NULL;

  /* User file descriptor is open? */
  if (openfiles[fd].handle == -1)
    return NULL;

  /* Valid. */
  return &openfiles[fd];
}

/* Return the next lowest numbered free file
   structure, or -1 if we can't find one. */
static int
newslot (void)
{
  int i;

  for (i = 0; i < MAX_OPEN_FILES; i++)
    if (openfiles[i].handle == -1)
      break;

  if (i == MAX_OPEN_FILES)
    return -1;

  return i;
}

void
initialise_monitor_handles (void)
{
  int i;

  /* Open the standard file descriptors by opening the special
   * teletype device, ":tt", read-only to obtain a descritpor for
   * standard input and write-only to obtain a descriptor for standard
   * output. Finally, open ":tt" in append mode to obtain a descriptor
   * for standard error. Since this is a write mode, most kernels will
   * probably return the same value as for standard output, but the
   * kernel can differentiate the two using the mode flag and return a
   * different descriptor for standard error.
   */

  param_block_t block[3];

  block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
  block[2] = 3;			/* length of filename */
  block[1] = 0;			/* mode "r" */
  monitor_stdin = do_AngelSVC (AngelSVC_Reason_Open, block);

  for (i = 0; i < MAX_OPEN_FILES; i++)
    openfiles[i].handle = -1;;

  if (_has_ext_stdout_stderr ())
  {
    block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
    block[2] = 3;			/* length of filename */
    block[1] = 4;			/* mode "w" */
    monitor_stdout = do_AngelSVC (AngelSVC_Reason_Open, block);

    block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
    block[2] = 3;			/* length of filename */
    block[1] = 8;			/* mode "a" */
    monitor_stderr = do_AngelSVC (AngelSVC_Reason_Open, block);
  }

  /* If we failed to open stderr, redirect to stdout. */
  if (monitor_stderr == -1)
    monitor_stderr = monitor_stdout;

  openfiles[0].handle = monitor_stdin;
  openfiles[0].flags = _FREAD;
  openfiles[0].pos = 0;

  if (_has_ext_stdout_stderr ())
  {
    openfiles[1].handle = monitor_stdout;
    openfiles[0].flags = _FWRITE;
    openfiles[1].pos = 0;
    openfiles[2].handle = monitor_stderr;
    openfiles[0].flags = _FWRITE;
    openfiles[2].pos = 0;
  }
}

int
_has_ext_exit_extended (void)
{
  if (supports_ext_exit_extended < 0)
  {
    initialise_semihosting_exts ();
  }

  return supports_ext_exit_extended;
}

int
_has_ext_stdout_stderr (void)
{
  if (supports_ext_stdout_stderr < 0)
  {
    initialise_semihosting_exts ();
  }

  return supports_ext_stdout_stderr;
}

static void
initialise_semihosting_exts (void)
{
  supports_ext_exit_extended = 0;
  supports_ext_stdout_stderr = 1;

#if SEMIHOST_V2
  char features[1];
  if (_get_semihosting_exts (features, 0, 1) > 0)
  {
     supports_ext_exit_extended
       = features[0] & (1 << SH_EXT_EXIT_EXTENDED_BITNUM);
     supports_ext_stdout_stderr
       = features[0] & (1 << SH_EXT_STDOUT_STDERR_BITNUM);
  }
#endif
}

int
_get_semihosting_exts (char* features, int offset, int num)
{
  int fd = _open (":semihosting-features", O_RDONLY);
  memset (features, 0, num);

  if (fd == -1)
  {
    return -1;
  }

  struct fdent *pfd;
  pfd = findslot (fd);

  param_block_t block[1];
  block[0] = pfd->handle;

  int len = do_AngelSVC (AngelSVC_Reason_FLen, block);

  if (len < NUM_SHFB_MAGIC
      || num > (len - NUM_SHFB_MAGIC))
  {
     _close (fd);
     return -1;
  }

  char buffer[NUM_SHFB_MAGIC];
  int n_read = _read (fd, buffer, NUM_SHFB_MAGIC);

  if (n_read < NUM_SHFB_MAGIC
      || buffer[0] != SHFB_MAGIC_0
      || buffer[1] != SHFB_MAGIC_1
      || buffer[2] != SHFB_MAGIC_2
      || buffer[3] != SHFB_MAGIC_3)
  {
     _close (fd);
     return -1;
  }

  if (_lseek (fd, offset, SEEK_CUR) < 0)
  {
     _close (fd);
     return -1;
  }

  n_read = _read (fd, features, num);

  _close (fd);

  return checkerror (n_read);
}

static int
get_errno (void)
{
  return do_AngelSVC (AngelSVC_Reason_Errno, NULL);
}

/* Set errno and return result. */
static int
error (int result)
{
  errno = get_errno ();
  return result;
}

/* Check the return and set errno appropriately. */
static int
checkerror (int result)
{
  if (result == -1)
    return error (-1);
  return result;
}

/* fh, is a valid internal file handle.
   ptr, is a null terminated string.
   len, is the length in bytes to read.
   Returns the number of bytes *not* written. */
int
_swiread (int fh, char *ptr, int len)
{
  param_block_t block[3];

  block[0] = fh;
  block[1] = POINTER_TO_PARAM_BLOCK_T (ptr);
  block[2] = len;

  return checkerror (do_AngelSVC (AngelSVC_Reason_Read, block));
}

/* fd, is a valid user file handle.
   Translates the return of _swiread into
   bytes read. */
int
_read (int fd, char *ptr, int len)
{
  int res;
  struct fdent *pfd;

  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return -1;
    }

  res = _swiread (pfd->handle, ptr, len);

  if (res == -1)
    return res;

  pfd->pos += len - res;

  /* res == len is not an error,
     at least if we want feof() to work.  */
  return len - res;
}

/* fd, is a user file descriptor. */
int
_swilseek (int fd, int ptr, int dir)
{
  int res;
  struct fdent *pfd;

  /* Valid file descriptor? */
  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return -1;
    }

  /* Valid whence? */
  if ((dir != SEEK_CUR) && (dir != SEEK_SET) && (dir != SEEK_END))
    {
      errno = EINVAL;
      return -1;
    }

  /* Convert SEEK_CUR to SEEK_SET */
  if (dir == SEEK_CUR)
    {
      ptr = pfd->pos + ptr;
      /* The resulting file offset would be negative. */
      if (ptr < 0)
	{
	  errno = EINVAL;
	  if ((pfd->pos > 0) && (ptr > 0))
	    errno = EOVERFLOW;
	  return -1;
	}
      dir = SEEK_SET;
    }

  param_block_t block[2];
  if (dir == SEEK_END)
    {
      block[0] = pfd->handle;
      res = checkerror (do_AngelSVC (AngelSVC_Reason_FLen, block));
      if (res == -1)
	return -1;
      ptr += res;
    }

  /* This code only does absolute seeks.  */
  block[0] = pfd->handle;
  block[1] = ptr;
  res = checkerror (do_AngelSVC (AngelSVC_Reason_Seek, block));
  /* At this point ptr is the current file position. */
  if (res >= 0)
    {
      pfd->pos = ptr;
      return ptr;
    }
  else
    return -1;
}

_lseek (int fd, int ptr, int dir)
{
  return _swilseek (fd, ptr, dir);
}

/* fh, is a valid internal file handle.
   Returns the number of bytes *not* written. */
int
_swiwrite (int fh, char *ptr, int len)
{
  param_block_t block[3];

  block[0] = fh;
  block[1] = POINTER_TO_PARAM_BLOCK_T (ptr);
  block[2] = len;

  return checkerror (do_AngelSVC (AngelSVC_Reason_Write, block));
}

/* fd, is a user file descriptor. */
int
_write (int fd, char *ptr, int len)
{
  int res;
  struct fdent *pfd;

  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return -1;
    }

  res = _swiwrite (pfd->handle, ptr, len);

  /* Clearly an error. */
  if (res < 0)
    return -1;

  pfd->pos += len - res;

  /* We wrote 0 bytes?
     Retrieve errno just in case. */
  if ((len - res) == 0)
    return error (0);

  return (len - res);
}

int
_swiopen (const char *path, int flags)
{
  int aflags = 0, fh;
  param_block_t block[3];
  static ino_t ino = 1;
  int fd;

  if (path == NULL)
    {
      errno = ENOENT;
      return -1;
    }

  fd = newslot ();

  if (fd == -1)
    {
      errno = EMFILE;
      return -1;
    }

  /* It is an error to open a file that already exists. */
  if ((flags & O_CREAT) && (flags & O_EXCL))
    {
      struct stat st;
      int res;
      res = _stat (path, &st);
      if (res != -1)
	{
	  errno = EEXIST;
	  return -1;
	}
    }

  /* The flags are Unix-style, so we need to convert them. */
#ifdef O_BINARY
  if (flags & O_BINARY)
    aflags |= 1;
#endif

  /* In O_RDONLY we expect aflags == 0. */

  if (flags & O_RDWR)
    aflags |= 2;

  if ((flags & O_CREAT) || (flags & O_TRUNC) || (flags & O_WRONLY))
    aflags |= 4;

  if (flags & O_APPEND)
    {
      /* Can't ask for w AND a; means just 'a'.  */
      aflags &= ~4;
      aflags |= 8;
    }

  block[0] = POINTER_TO_PARAM_BLOCK_T (path);
  block[2] = strlen (path);
  block[1] = aflags;

  fh = do_AngelSVC (AngelSVC_Reason_Open, block);

  /* Return a user file descriptor or an error. */
  if (fh >= 0)
    {
      openfiles[fd].handle = fh;
      openfiles[fd].flags = flags + 1;
      openfiles[fd].ino = ino++;
      openfiles[fd].pos = 0;
      return fd;
    }
  else
    return error (fh);
}

int
_open (const char *path, int flags, ...)
{
  return _swiopen (path, flags);
}

/* fh, is a valid internal file handle. */
int
_swiclose (int fh)
{
  param_block_t param[1];
  param[0] = fh;
  return checkerror (do_AngelSVC (AngelSVC_Reason_Close, param));
}

/* fd, is a user file descriptor. */
int
_close (int fd)
{
  int res;
  struct fdent *pfd;

  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return -1;
    }

  /* Handle stderr == stdout. */
  if ((fd == 1 || fd == 2) && (openfiles[1].handle == openfiles[2].handle))
    {
      pfd->handle = -1;
      return 0;
    }

  /* Attempt to close the handle. */
  res = _swiclose (pfd->handle);

  /* Reclaim handle? */
  if (res == 0)
    pfd->handle = -1;

  return res;
}

int __attribute__((weak))
_getpid (int n __attribute__ ((unused)))
{
  return 1;
}

/* Heap limit returned from SYS_HEAPINFO Angel semihost call.  */
ulong __heap_limit __attribute__ ((aligned (8))) = 0xcafedead;

caddr_t
_sbrk (int incr)
{
  extern char end asm ("end");	/* Defined by the linker.  */
  static char *heap_end;
  char *prev_heap_end;

  if (heap_end == NULL)
    heap_end = &end;

  prev_heap_end = heap_end;

  if ((heap_end + incr > stack_ptr)
      /* Honour heap limit if it's valid.  */
      || ((__heap_limit != 0xcafedead) && (heap_end + incr > __heap_limit)))
    {
      /* Some of the libstdc++-v3 tests rely upon detecting
         out of memory errors, so do not abort here.  */
      errno = ENOMEM;
      return (caddr_t) - 1;
    }

  heap_end += incr;

  return (caddr_t) prev_heap_end;
}

int
_swistat (int fd, struct stat *st)
{
  struct fdent *pfd;
  param_block_t param[1];
  int res;

  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return -1;
    }

  param[0] = pfd->handle;
  res = do_AngelSVC (AngelSVC_Reason_IsTTY, param);
  if (res != 0 && res != 1)
    return error (-1);

  memset (st, 0, sizeof (*st));

  if (res)
    {
      /* This is a tty. */
      st->st_mode |= S_IFCHR;
    }
  else
    {
      /* This is a file, return the file length.  */
      st->st_mode |= S_IFREG;
      res = checkerror (do_AngelSVC (AngelSVC_Reason_FLen, param));
      if (res == -1)
	return -1;
      st->st_size = res;
      st->st_blksize = 1024;
      st->st_blocks = (res + 1023) / 1024;
    }

  /* Deduce permissions based on mode in which file opened.  */
  st->st_mode |= S_IRUSR | S_IRGRP | S_IROTH;
  if (pfd->flags & _FWRITE)
    st->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;

  st->st_ino = pfd->ino;
  st->st_nlink = 1;
  return 0;
}

int __attribute__((weak))
_fstat (int fd, struct stat * st)
{
  return _swistat (fd, st);
}

int __attribute__((weak))
_stat (const char *fname, struct stat *st)
{
  int fd, res;
  /* The best we can do is try to open the file readonly.
     If it exists, then we can guess a few things about it. */
  if ((fd = _open (fname, O_RDONLY)) == -1)
    return -1;
  res = _swistat (fd, st);
  /* Not interested in the error. */
  _close (fd);
  return res;
}

int __attribute__((weak))
_link (void)
{
  errno = ENOSYS;
  return -1;
}

int
_unlink (const char *path)
{
  int res;
  param_block_t block[2];
  block[0] = POINTER_TO_PARAM_BLOCK_T (path);
  block[1] = strlen (path);
  res = do_AngelSVC (AngelSVC_Reason_Remove, block);
  if (res == -1)
    return error (res);
  return 0;
}

int
_gettimeofday (struct timeval *tp, void *tzvp)
{
  struct timezone *tzp = tzvp;
  if (tp)
    {
      /* Ask the host for the seconds since the Unix epoch.  */
      tp->tv_sec = do_AngelSVC (AngelSVC_Reason_Time, NULL);
      tp->tv_usec = 0;
    }

  /* Return fixed data for the timezone.  */
  if (tzp)
    {
      tzp->tz_minuteswest = 0;
      tzp->tz_dsttime = 0;
    }

  return 0;
}

/* Return a clock that ticks at 100Hz.  */
clock_t
_clock (void)
{
  clock_t timeval;

  timeval = do_AngelSVC (AngelSVC_Reason_Clock, NULL);
  return timeval;
}

/* Return a clock that ticks at 100Hz.  */
clock_t
_times (struct tms * tp)
{
  clock_t timeval = _clock ();

  if (tp)
    {
      tp->tms_utime = timeval;	/* user time */
      tp->tms_stime = 0;	/* system time */
      tp->tms_cutime = 0;	/* user time, children */
      tp->tms_cstime = 0;	/* system time, children */
    }

  return timeval;
};


int
_isatty (int fd)
{
  struct fdent *pfd;
  param_block_t param[1];
  int res;

  /* Return 1 if fd is an open file descriptor referring to a terminal;
     otherwise 0 is returned, and errno is set to indicate the error.  */

  pfd = findslot (fd);
  if (pfd == NULL)
    {
      errno = EBADF;
      return 0;
    }

  param[0] = pfd->handle;
  res = do_AngelSVC (AngelSVC_Reason_IsTTY, param);

  if (res != 1)
    return error (0);
  return res;
}

int
_system (const char *s)
{
  param_block_t block[2];
  int e;

  /* Hmmm.  The ARM debug interface specification doesn't say whether
     SYS_SYSTEM does the right thing with a null argument, or assign any
     meaning to its return value.  Try to do something reasonable....  */
  if (!s)
    return 1;			/* maybe there is a shell available? we can hope. :-P */
  block[0] = POINTER_TO_PARAM_BLOCK_T (s);
  block[1] = strlen (s);
  e = checkerror (do_AngelSVC (AngelSVC_Reason_System, block));
  if ((e >= 0) && (e < 256))
    {
      /* We have to convert e, an exit status to the encoded status of
         the command.  To avoid hard coding the exit status, we simply
         loop until we find the right position.  */
      int exit_code;

      for (exit_code = e; e && WEXITSTATUS (e) != exit_code; e <<= 1)
	continue;
    }
  return e;
}

int
_rename (const char *oldpath, const char *newpath)
{
  param_block_t block[4];
  block[0] = POINTER_TO_PARAM_BLOCK_T (oldpath);
  block[1] = strlen (oldpath);
  block[2] = POINTER_TO_PARAM_BLOCK_T (newpath);
  block[3] = strlen (newpath);
  return checkerror (do_AngelSVC (AngelSVC_Reason_Rename, block)) ? -1 : 0;
}

/* Returns the number of elapsed target ticks since the support code
   started execution. Returns -1 and sets errno on error.  */
long
__aarch64_angel_elapsed (void)
{
  int result;
  param_block_t block[2];
  result = checkerror (do_AngelSVC (AngelSVC_Reason_Elapsed, block));
  if (result == -1)
    return result;
  return block[0];
}
