target-mips: add Unified Hosting Interface (UHI) support

Add UHI semihosting support for MIPS. QEMU run with "-semihosting" option
will alter the behaviour of SDBBP 1 instruction -- UHI operation will be
called instead of generating a debug exception.

Also tweak Malta's pseudo-bootloader. On CPU reset the $4 register is set
to -1 if semihosting arguments are passed to indicate that the UHI
operations should be used to obtain input arguments.

Signed-off-by: Leon Alrae <leon.alrae@imgtec.com>
Reviewed-by: Aurelien Jarno <aurelien@aurel32.net>
This commit is contained in:
Leon Alrae 2015-06-19 11:08:43 +01:00
parent ff33476772
commit 3b3c1694cf
6 changed files with 408 additions and 26 deletions

View File

@ -53,6 +53,7 @@
#include "qemu/error-report.h"
#include "hw/empty_slot.h"
#include "sysemu/kvm.h"
#include "exec/semihost.h"
//#define DEBUG_BOARD_INIT
@ -634,7 +635,13 @@ static void write_bootloader (CPUMIPSState *env, uint8_t *base,
/* Second part of the bootloader */
p = (uint32_t *) (base + 0x580);
stl_p(p++, 0x24040002); /* addiu a0, zero, 2 */
if (semihosting_get_argc()) {
/* Preserve a0 content as arguments have been passed */
stl_p(p++, 0x00000000); /* nop */
} else {
stl_p(p++, 0x24040002); /* addiu a0, zero, 2 */
}
stl_p(p++, 0x3c1d0000 | (((ENVP_ADDR - 64) >> 16) & 0xffff)); /* lui sp, high(ENVP_ADDR) */
stl_p(p++, 0x37bd0000 | ((ENVP_ADDR - 64) & 0xffff)); /* ori sp, sp, low(ENVP_ADDR) */
stl_p(p++, 0x3c050000 | ((ENVP_ADDR >> 16) & 0xffff)); /* lui a1, high(ENVP_ADDR) */

View File

@ -3345,20 +3345,22 @@ Set OpenBIOS nvram @var{variable} to given @var{value} (PPC, SPARC only).
ETEXI
DEF("semihosting", 0, QEMU_OPTION_semihosting,
"-semihosting semihosting mode\n",
QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32)
QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 |
QEMU_ARCH_MIPS)
STEXI
@item -semihosting
@findex -semihosting
Enable semihosting mode (ARM, M68K, Xtensa only).
Enable semihosting mode (ARM, M68K, Xtensa, MIPS only).
ETEXI
DEF("semihosting-config", HAS_ARG, QEMU_OPTION_semihosting_config,
"-semihosting-config [enable=on|off][,target=native|gdb|auto][,arg=str[,...]]\n" \
" semihosting configuration\n",
QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32)
QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 |
QEMU_ARCH_MIPS)
STEXI
@item -semihosting-config [enable=on|off][,target=native|gdb|auto][,arg=str[,...]]
@findex -semihosting-config
Enable and configure semihosting (ARM, M68K, Xtensa only).
Enable and configure semihosting (ARM, M68K, Xtensa, MIPS only).
@table @option
@item target=@code{native|gdb|auto}
Defines where the semihosting calls will be addressed, to QEMU (@code{native})

View File

@ -1,4 +1,4 @@
obj-y += translate.o dsp_helper.o op_helper.o lmi_helper.o helper.o cpu.o
obj-y += gdbstub.o msa_helper.o
obj-y += gdbstub.o msa_helper.o mips-semi.o
obj-$(CONFIG_SOFTMMU) += machine.o
obj-$(CONFIG_KVM) += kvm.o

View File

@ -1,6 +1,8 @@
DEF_HELPER_3(raise_exception_err, noreturn, env, i32, int)
DEF_HELPER_2(raise_exception, noreturn, env, i32)
DEF_HELPER_1(do_semihosting, void, env)
#ifdef TARGET_MIPS64
DEF_HELPER_4(sdl, void, env, tl, tl, int)
DEF_HELPER_4(sdr, void, env, tl, tl, int)

336
target-mips/mips-semi.c Normal file
View File

@ -0,0 +1,336 @@
/*
* 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 <sys/stat.h>
#include "cpu.h"
#include "exec/helper-proto.h"
#include "exec/softmmu-semi.h"
#include "exec/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
};
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 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;
}
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;
break;
case UHI_read:
gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0);
gpr[3] = errno;
break;
case UHI_write:
gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0);
gpr[3] = errno;
break;
case UHI_lseek:
gpr[2] = lseek(gpr[4], gpr[5], gpr[6]);
gpr[3] = errno;
break;
case UHI_unlink:
GET_TARGET_STRING(p, gpr[4]);
gpr[2] = remove(p);
gpr[3] = 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;
if (gpr[2]) {
goto uhi_done;
}
gpr[2] = copy_stat_to_target(env, &sbuf, gpr[5]);
gpr[3] = 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_STRING(p, gpr[4]);
GET_TARGET_STRING(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;
break;
case UHI_pwrite:
gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
gpr[3] = errno;
break;
#ifndef _WIN32
case UHI_link:
GET_TARGET_STRING(p, gpr[4]);
GET_TARGET_STRING(p2, gpr[5]);
gpr[2] = link(p, p2);
gpr[3] = 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;
}

View File

@ -29,6 +29,7 @@
#include "exec/helper-proto.h"
#include "exec/helper-gen.h"
#include "sysemu/kvm.h"
#include "exec/semihost.h"
#include "trace-tcg.h"
@ -11549,6 +11550,15 @@ static int decode_extended_mips16_opc (CPUMIPSState *env, DisasContext *ctx)
return 4;
}
static inline bool is_uhi(int sdbbp_code)
{
#ifdef CONFIG_USER_ONLY
return false;
#else
return semihosting_enabled() && sdbbp_code == 1;
#endif
}
static int decode_mips16_opc (CPUMIPSState *env, DisasContext *ctx)
{
int rx, ry;
@ -11848,11 +11858,15 @@ static int decode_mips16_opc (CPUMIPSState *env, DisasContext *ctx)
}
break;
case RR_SDBBP:
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
if (is_uhi(extract32(ctx->opcode, 5, 6))) {
gen_helper_do_semihosting(cpu_env);
} else {
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
}
break;
case RR_SLT:
gen_slt(ctx, OPC_SLT, 24, rx, ry);
@ -12699,11 +12713,15 @@ static void gen_pool16c_insn(DisasContext *ctx)
generate_exception(ctx, EXCP_BREAK);
break;
case SDBBP16:
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
if (is_uhi(extract32(ctx->opcode, 0, 4))) {
gen_helper_do_semihosting(cpu_env);
} else {
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
}
break;
case JRADDIUSP + 0:
case JRADDIUSP + 1:
@ -13067,8 +13085,12 @@ static void gen_pool32axf (CPUMIPSState *env, DisasContext *ctx, int rt, int rs)
ctx->bstate = BS_STOP;
break;
case SDBBP:
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
if (is_uhi(extract32(ctx->opcode, 16, 10))) {
gen_helper_do_semihosting(cpu_env);
} else {
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
}
break;
default:
goto pool32axf_invalid;
@ -16460,10 +16482,14 @@ static void decode_opc_special_r6(CPUMIPSState *env, DisasContext *ctx)
}
break;
case R6_OPC_SDBBP:
if (ctx->hflags & MIPS_HFLAG_SBRI) {
generate_exception(ctx, EXCP_RI);
if (is_uhi(extract32(ctx->opcode, 6, 20))) {
gen_helper_do_semihosting(cpu_env);
} else {
generate_exception(ctx, EXCP_DBp);
if (ctx->hflags & MIPS_HFLAG_SBRI) {
generate_exception(ctx, EXCP_RI);
} else {
generate_exception(ctx, EXCP_DBp);
}
}
break;
#if defined(TARGET_MIPS64)
@ -16833,11 +16859,15 @@ static void decode_opc_special2_legacy(CPUMIPSState *env, DisasContext *ctx)
gen_cl(ctx, op1, rd, rs);
break;
case OPC_SDBBP:
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
if (is_uhi(extract32(ctx->opcode, 6, 20))) {
gen_helper_do_semihosting(cpu_env);
} else {
/* XXX: not clear which exception should be raised
* when in debug mode...
*/
check_insn(ctx, ISA_MIPS32);
generate_exception(ctx, EXCP_DBp);
}
break;
#if defined(TARGET_MIPS64)
case OPC_DCLO:
@ -19910,6 +19940,11 @@ void cpu_state_reset(CPUMIPSState *env)
restore_flush_mode(env);
restore_pamask(env);
cs->exception_index = EXCP_NONE;
if (semihosting_get_argc()) {
/* UHI interface can be used to obtain argc and argv */
env->active_tc.gpr[4] = -1;
}
}
void restore_state_to_opc(CPUMIPSState *env, TranslationBlock *tb, int pc_pos)