373 lines
11 KiB
C
373 lines
11 KiB
C
/*
|
|
* 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; \
|
|
return; \
|
|
} \
|
|
} 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; \
|
|
return; \
|
|
} \
|
|
p2 = lock_user_string(addr2); \
|
|
if (!p2) { \
|
|
unlock_user(p, addr, 0); \
|
|
gpr[2] = -1; \
|
|
gpr[3] = EFAULT; \
|
|
return; \
|
|
} \
|
|
} 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;
|
|
return;
|
|
}
|
|
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]) {
|
|
return;
|
|
}
|
|
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;
|
|
return;
|
|
}
|
|
gpr[2] = strlen(semihosting_get_arg(gpr[4]));
|
|
break;
|
|
case UHI_argn:
|
|
if (gpr[4] >= semihosting_get_argc()) {
|
|
gpr[2] = -1;
|
|
return;
|
|
}
|
|
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();
|
|
}
|
|
return;
|
|
}
|