/* * 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 . */ #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" 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; char *buf = g_malloc(char_num + 1); strncpy(buf, p, char_num); buf[char_num] = '\0'; gpr[2] = printf("%s%d%s", buf, (int)gpr[5], p2 + 2); g_free(buf); } else { gpr[2] = printf("%s", 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; }