text: /*
   * Unified Hosting Interface syscalls.
   *
   * Copyright (c) 2015 Imagination Technologies
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2 of the License, or (at your option) any later version.
   *
   * This library 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
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, see <http://www.gnu.org/licenses/>.
   */
  
  #include "qemu/osdep.h"
  #include "cpu.h"
  #include "qemu/log.h"
  #include "exec/helper-proto.h"
  #include "exec/softmmu-semi.h"
  #include "hw/semihosting/semihost.h"
  #include "hw/semihosting/console.h"
  
  typedef enum UHIOp {
      UHI_exit = 1,
      UHI_open = 2,
      UHI_close = 3,
      UHI_read = 4,
      UHI_write = 5,
      UHI_lseek = 6,
      UHI_unlink = 7,
      UHI_fstat = 8,
      UHI_argc = 9,
      UHI_argnlen = 10,
      UHI_argn = 11,
      UHI_plog = 13,
      UHI_assert = 14,
      UHI_pread = 19,
      UHI_pwrite = 20,
      UHI_link = 22
  } UHIOp;
  
  typedef struct UHIStat {
      int16_t uhi_st_dev;
      uint16_t uhi_st_ino;
      uint32_t uhi_st_mode;
      uint16_t uhi_st_nlink;
      uint16_t uhi_st_uid;
      uint16_t uhi_st_gid;
      int16_t uhi_st_rdev;
      uint64_t uhi_st_size;
      uint64_t uhi_st_atime;
      uint64_t uhi_st_spare1;
      uint64_t uhi_st_mtime;
      uint64_t uhi_st_spare2;
      uint64_t uhi_st_ctime;
      uint64_t uhi_st_spare3;
      uint64_t uhi_st_blksize;
      uint64_t uhi_st_blocks;
      uint64_t uhi_st_spare4[2];
  } UHIStat;
  
  enum UHIOpenFlags {
      UHIOpen_RDONLY = 0x0,
      UHIOpen_WRONLY = 0x1,
      UHIOpen_RDWR   = 0x2,
      UHIOpen_APPEND = 0x8,
      UHIOpen_CREAT  = 0x200,
      UHIOpen_TRUNC  = 0x400,
      UHIOpen_EXCL   = 0x800
  };
  
  /* Errno values taken from asm-mips/errno.h */
  static uint16_t host_to_mips_errno[] = {
      [ENAMETOOLONG] = 78,
  #ifdef EOVERFLOW
      [EOVERFLOW]    = 79,
  #endif
  #ifdef ELOOP
      [ELOOP]        = 90,
  #endif
  };
  
  static int errno_mips(int err)
  {
      if (err < 0 || err >= ARRAY_SIZE(host_to_mips_errno)) {
          return EINVAL;
      } else if (host_to_mips_errno[err]) {
          return host_to_mips_errno[err];
      } else {
          return err;
      }
  }
  
  static int copy_stat_to_target(CPUMIPSState *env, const struct stat *src,
                                 target_ulong vaddr)
  {
      hwaddr len = sizeof(struct UHIStat);
      UHIStat *dst = lock_user(VERIFY_WRITE, vaddr, len, 0);
      if (!dst) {
          errno = EFAULT;
          return -1;
      }
  
      dst->uhi_st_dev = tswap16(src->st_dev);
      dst->uhi_st_ino = tswap16(src->st_ino);
      dst->uhi_st_mode = tswap32(src->st_mode);
      dst->uhi_st_nlink = tswap16(src->st_nlink);
      dst->uhi_st_uid = tswap16(src->st_uid);
      dst->uhi_st_gid = tswap16(src->st_gid);
      dst->uhi_st_rdev = tswap16(src->st_rdev);
      dst->uhi_st_size = tswap64(src->st_size);
      dst->uhi_st_atime = tswap64(src->st_atime);
      dst->uhi_st_mtime = tswap64(src->st_mtime);
      dst->uhi_st_ctime = tswap64(src->st_ctime);
  #ifdef _WIN32
      dst->uhi_st_blksize = 0;
      dst->uhi_st_blocks = 0;
  #else
      dst->uhi_st_blksize = tswap64(src->st_blksize);
      dst->uhi_st_blocks = tswap64(src->st_blocks);
  #endif
      unlock_user(dst, vaddr, len);
      return 0;
  }
  
  static int get_open_flags(target_ulong target_flags)
  {
      int open_flags = 0;
  
      if (target_flags & UHIOpen_RDWR) {
          open_flags |= O_RDWR;
      } else if (target_flags & UHIOpen_WRONLY) {
          open_flags |= O_WRONLY;
      } else {
          open_flags |= O_RDONLY;
      }
  
      open_flags |= (target_flags & UHIOpen_APPEND) ? O_APPEND : 0;
      open_flags |= (target_flags & UHIOpen_CREAT)  ? O_CREAT  : 0;
      open_flags |= (target_flags & UHIOpen_TRUNC)  ? O_TRUNC  : 0;
      open_flags |= (target_flags & UHIOpen_EXCL)   ? O_EXCL   : 0;
  
      return open_flags;
  }
  
  static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr,
                           target_ulong len, target_ulong offset)
  {
      int num_of_bytes;
      void *dst = lock_user(VERIFY_READ, vaddr, len, 1);
      if (!dst) {
          errno = EFAULT;
          return -1;
      }
  
      if (offset) {
  #ifdef _WIN32
          num_of_bytes = 0;
  #else
          num_of_bytes = pwrite(fd, dst, len, offset);
  #endif
      } else {
          num_of_bytes = write(fd, dst, len);
      }
  
      unlock_user(dst, vaddr, 0);
      return num_of_bytes;
  }
  
  static int read_from_file(CPUMIPSState *env, target_ulong fd,
                            target_ulong vaddr, target_ulong len,
                            target_ulong offset)
  {
      int num_of_bytes;
      void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0);
      if (!dst) {
          errno = EFAULT;
          return -1;
      }
  
      if (offset) {
  #ifdef _WIN32
          num_of_bytes = 0;
  #else
          num_of_bytes = pread(fd, dst, len, offset);
  #endif
      } else {
          num_of_bytes = read(fd, dst, len);
      }
  
      unlock_user(dst, vaddr, len);
      return num_of_bytes;
  }
  
  static int copy_argn_to_target(CPUMIPSState *env, int arg_num,
                                 target_ulong vaddr)
  {
      int strsize = strlen(semihosting_get_arg(arg_num)) + 1;
      char *dst = lock_user(VERIFY_WRITE, vaddr, strsize, 0);
      if (!dst) {
          return -1;
      }
  
      strcpy(dst, semihosting_get_arg(arg_num));
  
      unlock_user(dst, vaddr, strsize);
      return 0;
  }
  
  #define GET_TARGET_STRING(p, addr)              \
      do {                                        \
          p = lock_user_string(addr);             \
          if (!p) {                               \
              gpr[2] = -1;                        \
              gpr[3] = EFAULT;                    \
              goto uhi_done;                      \
          }                                       \
      } while (0)
  
  #define GET_TARGET_STRINGS_2(p, addr, p2, addr2)        \
      do {                                                \
          p = lock_user_string(addr);                     \
          if (!p) {                                       \
              gpr[2] = -1;                                \
              gpr[3] = EFAULT;                            \
              goto uhi_done;                              \
          }                                               \
          p2 = lock_user_string(addr2);                   \
          if (!p2) {                                      \
              unlock_user(p, addr, 0);                    \
              gpr[2] = -1;                                \
              gpr[3] = EFAULT;                            \
              goto uhi_done;                              \
          }                                               \
      } while (0)
  
  #define FREE_TARGET_STRING(p, gpr)              \
      do {                                        \
          unlock_user(p, gpr, 0);                 \
      } while (0)
  
  void helper_do_semihosting(CPUMIPSState *env)
  {
      target_ulong *gpr = env->active_tc.gpr;
      const UHIOp op = gpr[25];
      char *p, *p2;
  
      switch (op) {
      case UHI_exit:
          qemu_log("UHI(%d): exit(%d)\n", op, (int)gpr[4]);
          exit(gpr[4]);
      case UHI_open:
          GET_TARGET_STRING(p, gpr[4]);
          if (!strcmp("/dev/stdin", p)) {
              gpr[2] = 0;
          } else if (!strcmp("/dev/stdout", p)) {
              gpr[2] = 1;
          } else if (!strcmp("/dev/stderr", p)) {
              gpr[2] = 2;
          } else {
              gpr[2] = open(p, get_open_flags(gpr[5]), gpr[6]);
              gpr[3] = errno_mips(errno);
          }
          FREE_TARGET_STRING(p, gpr[4]);
          break;
      case UHI_close:
          if (gpr[4] < 3) {
              /* ignore closing stdin/stdout/stderr */
              gpr[2] = 0;
              goto uhi_done;
          }
          gpr[2] = close(gpr[4]);
          gpr[3] = errno_mips(errno);
          break;
      case UHI_read:
          gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0);
          gpr[3] = errno_mips(errno);
          break;
      case UHI_write:
          gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0);
          gpr[3] = errno_mips(errno);
          break;
      case UHI_lseek:
          gpr[2] = lseek(gpr[4], gpr[5], gpr[6]);
          gpr[3] = errno_mips(errno);
          break;
      case UHI_unlink:
          GET_TARGET_STRING(p, gpr[4]);
          gpr[2] = remove(p);
          gpr[3] = errno_mips(errno);
          FREE_TARGET_STRING(p, gpr[4]);
          break;
      case UHI_fstat:
          {
              struct stat sbuf;
              memset(&sbuf, 0, sizeof(sbuf));
              gpr[2] = fstat(gpr[4], &sbuf);
              gpr[3] = errno_mips(errno);
              if (gpr[2]) {
                  goto uhi_done;
              }
              gpr[2] = copy_stat_to_target(env, &sbuf, gpr[5]);
              gpr[3] = errno_mips(errno);
          }
          break;
      case UHI_argc:
          gpr[2] = semihosting_get_argc();
          break;
      case UHI_argnlen:
          if (gpr[4] >= semihosting_get_argc()) {
              gpr[2] = -1;
              goto uhi_done;
          }
          gpr[2] = strlen(semihosting_get_arg(gpr[4]));
          break;
      case UHI_argn:
          if (gpr[4] >= semihosting_get_argc()) {
              gpr[2] = -1;
              goto uhi_done;
          }
          gpr[2] = copy_argn_to_target(env, gpr[4], gpr[5]);
          break;
      case UHI_plog:
          GET_TARGET_STRING(p, gpr[4]);
          p2 = strstr(p, "%d");
          if (p2) {
              int char_num = p2 - p;
              GString *s = g_string_new_len(p, char_num);
              g_string_append_printf(s, "%d%s", (int)gpr[5], p2 + 2);
              gpr[2] = qemu_semihosting_log_out(s->str, s->len);
              g_string_free(s, true);
          } else {
              gpr[2] = qemu_semihosting_log_out(p, strlen(p));
          }
          FREE_TARGET_STRING(p, gpr[4]);
          break;
      case UHI_assert:
          GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]);
          printf("assertion '");
          printf("\"%s\"", p);
          printf("': file \"%s\", line %d\n", p2, (int)gpr[6]);
          FREE_TARGET_STRING(p2, gpr[5]);
          FREE_TARGET_STRING(p, gpr[4]);
          abort();
          break;
      case UHI_pread:
          gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
          gpr[3] = errno_mips(errno);
          break;
      case UHI_pwrite:
          gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
          gpr[3] = errno_mips(errno);
          break;
  #ifndef _WIN32
      case UHI_link:
          GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]);
          gpr[2] = link(p, p2);
          gpr[3] = errno_mips(errno);
          FREE_TARGET_STRING(p2, gpr[5]);
          FREE_TARGET_STRING(p, gpr[4]);
          break;
  #endif
      default:
          fprintf(stderr, "Unknown UHI operation %d\n", op);
          abort();
      }
  uhi_done:
      return;
  }
  