diff options
author | Leon Alrae <leon.alrae@imgtec.com> | 2015-06-19 11:08:43 +0100 |
---|---|---|
committer | Leon Alrae <leon.alrae@imgtec.com> | 2015-06-26 09:08:50 +0100 |
commit | 3b3c1694cfd394b73de426edebdbf90c28f664fd (patch) | |
tree | dcd93bdb3b5bc6a41bf4d9c518cca24925d10b94 | |
parent | ff334767728011218c62f7476232d260cb5b28e6 (diff) |
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>
-rw-r--r-- | hw/mips/mips_malta.c | 9 | ||||
-rw-r--r-- | qemu-options.hx | 10 | ||||
-rw-r--r-- | target-mips/Makefile.objs | 2 | ||||
-rw-r--r-- | target-mips/helper.h | 2 | ||||
-rw-r--r-- | target-mips/mips-semi.c | 336 | ||||
-rw-r--r-- | target-mips/translate.c | 75 |
6 files changed, 408 insertions, 26 deletions
diff --git a/hw/mips/mips_malta.c b/hw/mips/mips_malta.c index a5d64d5030..3082e75340 100644 --- a/hw/mips/mips_malta.c +++ b/hw/mips/mips_malta.c @@ -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) */ diff --git a/qemu-options.hx b/qemu-options.hx index 987dbf0a8a..e6e3895918 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -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}) diff --git a/target-mips/Makefile.objs b/target-mips/Makefile.objs index 108fd9b501..bc5ed8511f 100644 --- a/target-mips/Makefile.objs +++ b/target-mips/Makefile.objs @@ -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 diff --git a/target-mips/helper.h b/target-mips/helper.h index 8df98c71b8..2b28e875cf 100644 --- a/target-mips/helper.h +++ b/target-mips/helper.h @@ -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) diff --git a/target-mips/mips-semi.c b/target-mips/mips-semi.c new file mode 100644 index 0000000000..6944e986b6 --- /dev/null +++ b/target-mips/mips-semi.c @@ -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; +} diff --git a/target-mips/translate.c b/target-mips/translate.c index 7d9f2dac6b..8547e2d04d 100644 --- a/target-mips/translate.c +++ b/target-mips/translate.c @@ -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) |