There is no "version 2" of the "Lesser" General Public License. It is either "GPL version 2.0" or "Lesser GPL version 2.1". This patch replaces all occurrences of "Lesser GPL version 2" with "Lesser GPL version 2.1" in comment section. Signed-off-by: Chetan Pant <chetan4windows@gmail.com> Reviewed-by: Thomas Huth <thuth@redhat.com> Message-Id: <20201016143509.26692-1-chetan4windows@gmail.com> [PMD: Split hw/ vs target/] Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
374 lines
11 KiB
C
374 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.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 "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;
|
|
}
|