| /* |
| * 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.1 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 "gdbstub/syscalls.h" |
| #include "gdbstub/helpers.h" |
| #include "semihosting/uaccess.h" |
| #include "semihosting/semihost.h" |
| #include "semihosting/console.h" |
| #include "semihosting/syscalls.h" |
| #include "internal.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 |
| }; |
| |
| enum UHIErrno { |
| UHI_EACCESS = 13, |
| UHI_EAGAIN = 11, |
| UHI_EBADF = 9, |
| UHI_EBADMSG = 77, |
| UHI_EBUSY = 16, |
| UHI_ECONNRESET = 104, |
| UHI_EEXIST = 17, |
| UHI_EFBIG = 27, |
| UHI_EINTR = 4, |
| UHI_EINVAL = 22, |
| UHI_EIO = 5, |
| UHI_EISDIR = 21, |
| UHI_ELOOP = 92, |
| UHI_EMFILE = 24, |
| UHI_EMLINK = 31, |
| UHI_ENAMETOOLONG = 91, |
| UHI_ENETDOWN = 115, |
| UHI_ENETUNREACH = 114, |
| UHI_ENFILE = 23, |
| UHI_ENOBUFS = 105, |
| UHI_ENOENT = 2, |
| UHI_ENOMEM = 12, |
| UHI_ENOSPC = 28, |
| UHI_ENOSR = 63, |
| UHI_ENOTCONN = 128, |
| UHI_ENOTDIR = 20, |
| UHI_ENXIO = 6, |
| UHI_EOVERFLOW = 139, |
| UHI_EPERM = 1, |
| UHI_EPIPE = 32, |
| UHI_ERANGE = 34, |
| UHI_EROFS = 30, |
| UHI_ESPIPE = 29, |
| UHI_ETIMEDOUT = 116, |
| UHI_ETXTBSY = 26, |
| UHI_EWOULDBLOCK = 11, |
| UHI_EXDEV = 18, |
| }; |
| |
| static void report_fault(CPUMIPSState *env) |
| { |
| int op = env->active_tc.gpr[25]; |
| error_report("Fault during UHI operation %d", op); |
| abort(); |
| } |
| |
| static void uhi_cb(CPUState *cs, uint64_t ret, int err) |
| { |
| CPUMIPSState *env = cpu_env(cs); |
| |
| #define E(N) case E##N: err = UHI_E##N; break |
| |
| switch (err) { |
| case 0: |
| break; |
| E(PERM); |
| E(NOENT); |
| E(INTR); |
| E(BADF); |
| E(BUSY); |
| E(EXIST); |
| E(NOTDIR); |
| E(ISDIR); |
| E(INVAL); |
| E(NFILE); |
| E(MFILE); |
| E(FBIG); |
| E(NOSPC); |
| E(SPIPE); |
| E(ROFS); |
| E(NAMETOOLONG); |
| default: |
| err = UHI_EINVAL; |
| break; |
| case EFAULT: |
| report_fault(env); |
| } |
| |
| #undef E |
| |
| env->active_tc.gpr[2] = ret; |
| env->active_tc.gpr[3] = err; |
| } |
| |
| static void uhi_fstat_cb(CPUState *cs, uint64_t ret, int err) |
| { |
| QEMU_BUILD_BUG_ON(sizeof(UHIStat) < sizeof(struct gdb_stat)); |
| |
| if (!err) { |
| CPUMIPSState *env = cpu_env(cs); |
| target_ulong addr = env->active_tc.gpr[5]; |
| UHIStat *dst = lock_user(VERIFY_WRITE, addr, sizeof(UHIStat), 1); |
| struct gdb_stat s; |
| |
| if (!dst) { |
| report_fault(env); |
| } |
| |
| memcpy(&s, dst, sizeof(struct gdb_stat)); |
| memset(dst, 0, sizeof(UHIStat)); |
| |
| dst->uhi_st_dev = tswap16(be32_to_cpu(s.gdb_st_dev)); |
| dst->uhi_st_ino = tswap16(be32_to_cpu(s.gdb_st_ino)); |
| dst->uhi_st_mode = tswap32(be32_to_cpu(s.gdb_st_mode)); |
| dst->uhi_st_nlink = tswap16(be32_to_cpu(s.gdb_st_nlink)); |
| dst->uhi_st_uid = tswap16(be32_to_cpu(s.gdb_st_uid)); |
| dst->uhi_st_gid = tswap16(be32_to_cpu(s.gdb_st_gid)); |
| dst->uhi_st_rdev = tswap16(be32_to_cpu(s.gdb_st_rdev)); |
| dst->uhi_st_size = tswap64(be64_to_cpu(s.gdb_st_size)); |
| dst->uhi_st_atime = tswap64(be32_to_cpu(s.gdb_st_atime)); |
| dst->uhi_st_mtime = tswap64(be32_to_cpu(s.gdb_st_mtime)); |
| dst->uhi_st_ctime = tswap64(be32_to_cpu(s.gdb_st_ctime)); |
| dst->uhi_st_blksize = tswap64(be64_to_cpu(s.gdb_st_blksize)); |
| dst->uhi_st_blocks = tswap64(be64_to_cpu(s.gdb_st_blocks)); |
| |
| unlock_user(dst, addr, sizeof(UHIStat)); |
| } |
| |
| uhi_cb(cs, ret, err); |
| } |
| |
| void mips_semihosting(CPUMIPSState *env) |
| { |
| CPUState *cs = env_cpu(env); |
| target_ulong *gpr = env->active_tc.gpr; |
| const UHIOp op = gpr[25]; |
| char *p; |
| |
| switch (op) { |
| case UHI_exit: |
| gdb_exit(gpr[4]); |
| exit(gpr[4]); |
| |
| case UHI_open: |
| { |
| target_ulong fname = gpr[4]; |
| int ret = -1; |
| |
| p = lock_user_string(fname); |
| if (!p) { |
| report_fault(env); |
| } |
| if (!strcmp("/dev/stdin", p)) { |
| ret = 0; |
| } else if (!strcmp("/dev/stdout", p)) { |
| ret = 1; |
| } else if (!strcmp("/dev/stderr", p)) { |
| ret = 2; |
| } |
| unlock_user(p, fname, 0); |
| |
| /* FIXME: reusing a guest fd doesn't seem correct. */ |
| if (ret >= 0) { |
| gpr[2] = ret; |
| break; |
| } |
| |
| semihost_sys_open(cs, uhi_cb, fname, 0, gpr[5], gpr[6]); |
| } |
| break; |
| |
| case UHI_close: |
| semihost_sys_close(cs, uhi_cb, gpr[4]); |
| break; |
| case UHI_read: |
| semihost_sys_read(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); |
| break; |
| case UHI_write: |
| semihost_sys_write(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); |
| break; |
| case UHI_lseek: |
| semihost_sys_lseek(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); |
| break; |
| case UHI_unlink: |
| semihost_sys_remove(cs, uhi_cb, gpr[4], 0); |
| break; |
| case UHI_fstat: |
| semihost_sys_fstat(cs, uhi_fstat_cb, gpr[4], gpr[5]); |
| break; |
| |
| case UHI_argc: |
| gpr[2] = semihosting_get_argc(); |
| break; |
| case UHI_argnlen: |
| { |
| const char *s = semihosting_get_arg(gpr[4]); |
| gpr[2] = s ? strlen(s) : -1; |
| } |
| break; |
| case UHI_argn: |
| { |
| const char *s = semihosting_get_arg(gpr[4]); |
| target_ulong addr; |
| size_t len; |
| |
| if (!s) { |
| gpr[2] = -1; |
| break; |
| } |
| len = strlen(s) + 1; |
| addr = gpr[5]; |
| p = lock_user(VERIFY_WRITE, addr, len, 0); |
| if (!p) { |
| report_fault(env); |
| } |
| memcpy(p, s, len); |
| unlock_user(p, addr, len); |
| gpr[2] = 0; |
| } |
| break; |
| |
| case UHI_plog: |
| { |
| target_ulong addr = gpr[4]; |
| ssize_t len = target_strlen(addr); |
| GString *str; |
| char *pct_d; |
| |
| if (len < 0) { |
| report_fault(env); |
| } |
| p = lock_user(VERIFY_READ, addr, len, 1); |
| if (!p) { |
| report_fault(env); |
| } |
| |
| pct_d = strstr(p, "%d"); |
| if (!pct_d) { |
| unlock_user(p, addr, 0); |
| semihost_sys_write(cs, uhi_cb, 2, addr, len); |
| break; |
| } |
| |
| str = g_string_new_len(p, pct_d - p); |
| g_string_append_printf(str, "%d%s", (int)gpr[5], pct_d + 2); |
| unlock_user(p, addr, 0); |
| |
| /* |
| * When we're using gdb, we need a guest address, so |
| * drop the string onto the stack below the stack pointer. |
| */ |
| if (use_gdb_syscalls()) { |
| addr = gpr[29] - str->len; |
| p = lock_user(VERIFY_WRITE, addr, str->len, 0); |
| if (!p) { |
| report_fault(env); |
| } |
| memcpy(p, str->str, str->len); |
| unlock_user(p, addr, str->len); |
| semihost_sys_write(cs, uhi_cb, 2, addr, str->len); |
| } else { |
| gpr[2] = qemu_semihosting_console_write(str->str, str->len); |
| } |
| g_string_free(str, true); |
| } |
| break; |
| |
| case UHI_assert: |
| { |
| const char *msg, *file; |
| |
| msg = lock_user_string(gpr[4]); |
| if (!msg) { |
| msg = "<EFAULT>"; |
| } |
| file = lock_user_string(gpr[5]); |
| if (!file) { |
| file = "<EFAULT>"; |
| } |
| |
| error_report("UHI assertion \"%s\": file \"%s\", line %d", |
| msg, file, (int)gpr[6]); |
| abort(); |
| } |
| |
| default: |
| error_report("Unknown UHI operation %d", op); |
| abort(); |
| } |
| return; |
| } |