On Fri, Oct 23, 2020 at 2:51 PM Keith Packard via <qemu-devel@nongnu.org> wrote: > > Adapt the arm semihosting support code for RISCV. This implementation > is based on the standard for RISC-V semihosting version 0.2 as > documented in > > https://github.com/riscv/riscv-semihosting-spec/releases/tag/0.2 > > Signed-off-by: Keith Packard <kei...@keithp.com>
Hey Keith, > > --- > > v2: > Update PC after exception is handled to follow > change in the ARM version for SYS_READC > > v3: > Disallow semihosting in user mode; report a regular > breakpoint in that case. > > v4: > Fix errors reported by checkpatch > > v5: > Reference current RISC-V semihosting specification > > v6: > Add support for semihosting in riscv64-linux-user and > riscv32-linux-user > > v7: > Add meson build support > > v8: > Fix errors reported by checkpatch that crept in. > --- > MAINTAINERS | 1 + > default-configs/devices/riscv32-softmmu.mak | 1 + > default-configs/devices/riscv64-softmmu.mak | 1 + > linux-user/meson.build | 1 + > linux-user/qemu.h | 4 +- > linux-user/riscv/meson.build | 2 + > linux-user/riscv/semihost.c | 76 ++ > qemu-options.hx | 10 +- > target/riscv/cpu.h | 7 + > target/riscv/cpu_bits.h | 1 + > target/riscv/cpu_helper.c | 9 + > .../riscv/insn_trans/trans_privileged.c.inc | 24 +- > target/riscv/meson.build | 1 + > target/riscv/riscv-semi.c | 1086 +++++++++++++++++ > target/riscv/translate.c | 11 + > 15 files changed, 1229 insertions(+), 6 deletions(-) > create mode 100644 linux-user/riscv/meson.build > create mode 100644 linux-user/riscv/semihost.c > create mode 100644 target/riscv/riscv-semi.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 6a197bd358..8cf5b1e448 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -279,6 +279,7 @@ M: Palmer Dabbelt <pal...@dabbelt.com> > M: Alistair Francis <alistair.fran...@wdc.com> > M: Sagar Karandikar <sag...@eecs.berkeley.edu> > M: Bastian Koppelmann <kbast...@mail.uni-paderborn.de> > +M: Keith Packard <kei...@keithp.com> I don't think you should be a maintainer just yet. In general people have to be actively reviewing patches to be listed as a maintainer. > L: qemu-ri...@nongnu.org > S: Supported > F: target/riscv/ > diff --git a/default-configs/devices/riscv32-softmmu.mak > b/default-configs/devices/riscv32-softmmu.mak > index 94a236c9c2..e8a4eeebec 100644 > --- a/default-configs/devices/riscv32-softmmu.mak > +++ b/default-configs/devices/riscv32-softmmu.mak > @@ -3,6 +3,7 @@ > # Uncomment the following lines to disable these optional devices: > # > #CONFIG_PCI_DEVICES=n > +CONFIG_SEMIHOSTING=y > > # Boards: > # > diff --git a/default-configs/devices/riscv64-softmmu.mak > b/default-configs/devices/riscv64-softmmu.mak > index 76b6195648..d8a87b7671 100644 > --- a/default-configs/devices/riscv64-softmmu.mak > +++ b/default-configs/devices/riscv64-softmmu.mak > @@ -3,6 +3,7 @@ > # Uncomment the following lines to disable these optional devices: > # > #CONFIG_PCI_DEVICES=n > +CONFIG_SEMIHOSTING=y > > # Boards: > # > diff --git a/linux-user/meson.build b/linux-user/meson.build > index 2b94e4ba24..31b5ed99b5 100644 > --- a/linux-user/meson.build > +++ b/linux-user/meson.build > @@ -29,6 +29,7 @@ subdir('microblaze') > subdir('mips64') > subdir('mips') > subdir('ppc') > +subdir('riscv') > subdir('s390x') > subdir('sh4') > subdir('sparc64') > diff --git a/linux-user/qemu.h b/linux-user/qemu.h > index 941ca99722..faeaab9614 100644 > --- a/linux-user/qemu.h > +++ b/linux-user/qemu.h > @@ -105,6 +105,8 @@ typedef struct TaskState { > /* FPA state */ > FPA11 fpa; > # endif > +#endif > +#if defined(TARGET_ARM) || defined(TARGET_RISCV) > int swi_errno; > #endif > #if defined(TARGET_I386) && !defined(TARGET_X86_64) > @@ -118,7 +120,7 @@ typedef struct TaskState { > #ifdef TARGET_M68K > abi_ulong tp_value; > #endif > -#if defined(TARGET_ARM) || defined(TARGET_M68K) > +#if defined(TARGET_ARM) || defined(TARGET_M68K) || defined(TARGET_RISCV) > /* Extra fields for semihosted binaries. */ > abi_ulong heap_base; > abi_ulong heap_limit; > diff --git a/linux-user/riscv/meson.build b/linux-user/riscv/meson.build > new file mode 100644 > index 0000000000..aee3179cdb > --- /dev/null > +++ b/linux-user/riscv/meson.build > @@ -0,0 +1,2 @@ > +linux_user_ss.add(when: 'TARGET_RISCV64', if_true: files('semihost.c')) > +linux_user_ss.add(when: 'TARGET_RISCV32', if_true: files('semihost.c')) > diff --git a/linux-user/riscv/semihost.c b/linux-user/riscv/semihost.c > new file mode 100644 > index 0000000000..2d35ebab68 > --- /dev/null > +++ b/linux-user/riscv/semihost.c > @@ -0,0 +1,76 @@ > +/* > + * RISC-V Semihosting Console Support > + * > + * Copyright (c) 2019 Linaro Ltd > + * > + * Currently RISC-V and ARM are unique in having support for semihosting > support > + * in linux-user. So for now we implement the common console API but > + * just for risc-v and arm linux-user. > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "cpu.h" > +#include "hw/semihosting/console.h" > +#include "qemu.h" > +#include <termios.h> > + > +int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) > +{ > + int len = target_strlen(addr); > + void *s; > + if (len < 0) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: passed inaccessible address " TARGET_FMT_lx, > + __func__, addr); > + return 0; > + } > + s = lock_user(VERIFY_READ, addr, (long)(len + 1), 1); > + g_assert(s); /* target_strlen has already verified this will work */ > + len = write(STDERR_FILENO, s, len); > + unlock_user(s, addr, 0); > + return len; > +} > + > +void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) > +{ > + char c; > + > + if (get_user_u8(c, addr)) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "%s: passed inaccessible address " TARGET_FMT_lx, > + __func__, addr); > + } else { > + if (write(STDERR_FILENO, &c, 1) != 1) { > + qemu_log_mask(LOG_UNIMP, "%s: unexpected write to stdout > failure", > + __func__); > + } > + } > +} > + > +/* > + * For linux-user we can safely block. However as we want to return as > + * soon as a character is read we need to tweak the termio to disable > + * line buffering. We restore the old mode afterwards in case the > + * program is expecting more normal behaviour. This is slow but > + * nothing using semihosting console reading is expecting to be fast. > + */ > +target_ulong qemu_semihosting_console_inc(CPUArchState *env) > +{ > + uint8_t c; > + struct termios old_tio, new_tio; > + > + /* Disable line-buffering and echo */ > + tcgetattr(STDIN_FILENO, &old_tio); > + new_tio = old_tio; > + new_tio.c_lflag &= (~ICANON & ~ECHO); > + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); > + > + c = getchar(); > + > + /* restore config */ > + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); > + > + return (target_ulong) c; > +} > diff --git a/qemu-options.hx b/qemu-options.hx > index 1da52a269c..0dcf15b480 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -4192,10 +4192,10 @@ ERST > DEF("semihosting", 0, QEMU_OPTION_semihosting, > "-semihosting semihosting mode\n", > QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 | > - QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2) > + QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV) > SRST > ``-semihosting`` > - Enable semihosting mode (ARM, M68K, Xtensa, MIPS, Nios II only). > + Enable semihosting mode (ARM, M68K, Xtensa, MIPS, Nios II, RISC-V only). > > Note that this allows guest direct access to the host filesystem, so > should only be used with a trusted guest OS. > @@ -4207,10 +4207,10 @@ DEF("semihosting-config", HAS_ARG, > QEMU_OPTION_semihosting_config, > "-semihosting-config > [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]\n" \ > " semihosting configuration\n", > QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 | > -QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2) > +QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV) > SRST > ``-semihosting-config > [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]`` > - Enable and configure semihosting (ARM, M68K, Xtensa, MIPS, Nios II > + Enable and configure semihosting (ARM, M68K, Xtensa, MIPS, Nios II, > RISC-V > only). > > Note that this allows guest direct access to the host filesystem, so > @@ -4225,6 +4225,8 @@ SRST > open/read/write/seek/select. Tensilica baremetal libc for ISS and > linux platform "sim" use this interface. > > + On RISC-V this implements the standard semihosting API, version 0.2. > + > ``target=native|gdb|auto`` > Defines where the semihosting calls will be addressed, to QEMU > (``native``) or to GDB (``gdb``). The default is ``auto``, which > diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h > index de275782e6..2ab5220964 100644 > --- a/target/riscv/cpu.h > +++ b/target/riscv/cpu.h > @@ -460,4 +460,11 @@ void riscv_set_csr_ops(int csrno, riscv_csr_operations > *ops); > > void riscv_cpu_register_gdb_regs_for_features(CPUState *cs); > > +typedef CPURISCVState CPUArchState; > +typedef RISCVCPU ArchCPU; > + > +target_ulong do_riscv_semihosting(CPURISCVState *env); > + > +#include "exec/cpu-all.h" This isn't used in the header so it shouldn't be here. > + > #endif /* RISCV_CPU_H */ > diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h > index bd36062877..26a60b9e27 100644 > --- a/target/riscv/cpu_bits.h > +++ b/target/riscv/cpu_bits.h > @@ -556,6 +556,7 @@ > #define RISCV_EXCP_INST_PAGE_FAULT 0xc /* since: priv-1.10.0 */ > #define RISCV_EXCP_LOAD_PAGE_FAULT 0xd /* since: priv-1.10.0 */ > #define RISCV_EXCP_STORE_PAGE_FAULT 0xf /* since: priv-1.10.0 */ > +#define RISCV_EXCP_SEMIHOST 0x10 I don't see this in the RISC-V spec, it seems to just be reserved, not for semihosting. > #define RISCV_EXCP_INST_GUEST_PAGE_FAULT 0x14 > #define RISCV_EXCP_LOAD_GUEST_ACCESS_FAULT 0x15 > #define RISCV_EXCP_VIRT_INSTRUCTION_FAULT 0x16 > diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c > index 904899054d..ddb751df26 100644 > --- a/target/riscv/cpu_helper.c > +++ b/target/riscv/cpu_helper.c > @@ -856,6 +856,15 @@ void riscv_cpu_do_interrupt(CPUState *cs) > target_ulong htval = 0; > target_ulong mtval2 = 0; > > + if (cause == RISCV_EXCP_SEMIHOST) { > + if (env->priv >= PRV_S) { > + env->gpr[xA0] = do_riscv_semihosting(env); > + env->pc += 4; > + return; > + } > + cause = RISCV_EXCP_BREAKPOINT; > + } > + > if (!async) { > /* set tval to badaddr for traps with address information */ > switch (cause) { > diff --git a/target/riscv/insn_trans/trans_privileged.c.inc > b/target/riscv/insn_trans/trans_privileged.c.inc > index 2a61a853bf..8104f7b13c 100644 > --- a/target/riscv/insn_trans/trans_privileged.c.inc > +++ b/target/riscv/insn_trans/trans_privileged.c.inc > @@ -29,7 +29,29 @@ static bool trans_ecall(DisasContext *ctx, arg_ecall *a) > > static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a) > { > - generate_exception(ctx, RISCV_EXCP_BREAKPOINT); > + uint32_t pre = opcode_at(&ctx->base, ctx->base.pc_next - 4); > + uint32_t ebreak = opcode_at(&ctx->base, ctx->base.pc_next); > + uint32_t post = opcode_at(&ctx->base, ctx->base.pc_next + 4); > + > + /* > + * The RISC-V semihosting spec specifies the following > + * three-instruction sequence to flag a semihosting call: > + * > + * slli zero, zero, 0x1f 0x01f01013 > + * ebreak 0x00100073 > + * srai zero, zero, 0x7 0x40705013 > + * > + * The two shift operations on the zero register are no-ops, used > + * here to signify a semihosting exception, rather than a breakpoint. > + * > + * Uncompressed instructions are used so that the sequence is easy > + * to validate. > + */ > + if (pre == 0x01f01013 && ebreak == 0x00100073 && post == 0x40705013) { > + generate_exception(ctx, RISCV_EXCP_SEMIHOST); > + } else { > + generate_exception(ctx, RISCV_EXCP_BREAKPOINT); > + } > exit_tb(ctx); /* no chaining */ > ctx->base.is_jmp = DISAS_NORETURN; > return true; > diff --git a/target/riscv/meson.build b/target/riscv/meson.build > index abd647fea1..3d95282cb3 100644 > --- a/target/riscv/meson.build > +++ b/target/riscv/meson.build > @@ -22,6 +22,7 @@ riscv_ss.add(files( > 'op_helper.c', > 'vector_helper.c', > 'translate.c', > + 'riscv-semi.c' > )) > > riscv_softmmu_ss = ss.source_set() > diff --git a/target/riscv/riscv-semi.c b/target/riscv/riscv-semi.c > new file mode 100644 > index 0000000000..ea705899ea > --- /dev/null > +++ b/target/riscv/riscv-semi.c > @@ -0,0 +1,1086 @@ > +/* > + * RISC-V semihosting syscalls > + * > + * Copyright (c) 2005, 2007 CodeSourcery. > + * Copyright (c) 2019 Linaro > + * Copyright © 2019 Keith Packard > + * Written by Paul Brook. > + * Adapted for RISC-V by Keith Packard > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program 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 General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, see <http://www.gnu.org/licenses/>. > + * > + * ARM Semihosting is documented in: > + * Semihosting for AArch32 and AArch64 Release 2.0 > + * https://static.docs.arm.com/100863/0200/semihosting.pdf Maybe just point to the RISC-V doc instead. > + */ > + > +#include "qemu/osdep.h" > + > +#include "cpu.h" > +#include "hw/semihosting/semihost.h" > +#include "hw/semihosting/console.h" > +#include "qemu/log.h" > +#ifdef CONFIG_USER_ONLY > +#include "qemu.h" > + > +#define RISCV_HEAP_SIZE (128 * 1024 * 1024) > +#else > +#include "exec/gdbstub.h" > +#include "qemu/cutils.h" > +#endif > + > +#define TARGET_SYS_OPEN 0x01 > +#define TARGET_SYS_CLOSE 0x02 > +#define TARGET_SYS_WRITEC 0x03 > +#define TARGET_SYS_WRITE0 0x04 > +#define TARGET_SYS_WRITE 0x05 > +#define TARGET_SYS_READ 0x06 > +#define TARGET_SYS_READC 0x07 > +#define TARGET_SYS_ISTTY 0x09 > +#define TARGET_SYS_SEEK 0x0a > +#define TARGET_SYS_FLEN 0x0c > +#define TARGET_SYS_TMPNAM 0x0d > +#define TARGET_SYS_REMOVE 0x0e > +#define TARGET_SYS_RENAME 0x0f > +#define TARGET_SYS_CLOCK 0x10 > +#define TARGET_SYS_TIME 0x11 > +#define TARGET_SYS_SYSTEM 0x12 > +#define TARGET_SYS_ERRNO 0x13 > +#define TARGET_SYS_GET_CMDLINE 0x15 > +#define TARGET_SYS_HEAPINFO 0x16 > +#define TARGET_SYS_EXIT 0x18 > +#define TARGET_SYS_SYNCCACHE 0x19 > +#define TARGET_SYS_EXIT_EXTENDED 0x20 > + > +/* > + * ADP_Stopped_ApplicationExit is used for exit(0), > + * anything else is implemented as exit(1) > + */ > +#define ADP_Stopped_ApplicationExit (0x20026) > + > +#ifndef O_BINARY > +#define O_BINARY 0 > +#endif > + > +#define GDB_O_RDONLY 0x000 > +#define GDB_O_WRONLY 0x001 > +#define GDB_O_RDWR 0x002 > +#define GDB_O_APPEND 0x008 > +#define GDB_O_CREAT 0x200 > +#define GDB_O_TRUNC 0x400 > +#define GDB_O_BINARY 0 > + > +static int gdb_open_modeflags[12] = { > + GDB_O_RDONLY, > + GDB_O_RDONLY | GDB_O_BINARY, > + GDB_O_RDWR, > + GDB_O_RDWR | GDB_O_BINARY, > + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, > + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, > + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, > + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, > + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, > + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, > + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, > + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY > +}; > + > +static int open_modeflags[12] = { > + O_RDONLY, > + O_RDONLY | O_BINARY, > + O_RDWR, > + O_RDWR | O_BINARY, > + O_WRONLY | O_CREAT | O_TRUNC, > + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, > + O_RDWR | O_CREAT | O_TRUNC, > + O_RDWR | O_CREAT | O_TRUNC | O_BINARY, > + O_WRONLY | O_CREAT | O_APPEND, > + O_WRONLY | O_CREAT | O_APPEND | O_BINARY, > + O_RDWR | O_CREAT | O_APPEND, > + O_RDWR | O_CREAT | O_APPEND | O_BINARY > +}; > + > +typedef enum GuestFDType { > + GuestFDUnused = 0, > + GuestFDHost = 1, > + GuestFDGDB = 2, > + GuestFDFeatureFile = 3, > +} GuestFDType; > + > +/* > + * Guest file descriptors are integer indexes into an array of > + * these structures (we will dynamically resize as necessary). > + */ > +typedef struct GuestFD { > + GuestFDType type; > + union { > + int hostfd; > + target_ulong featurefile_offset; > + }; > +} GuestFD; > + > +static GArray *guestfd_array; > + > +#define FIRST_GUEST_FD 3 > + > +/* > + * Allocate a new guest file descriptor and return it; if we > + * couldn't allocate a new fd then return -1. > + * This is a fairly simplistic implementation because we don't > + * expect that most semihosting guest programs will make very > + * heavy use of opening and closing fds. > + */ > +static int alloc_guestfd(void) > +{ > + guint i; > + > + if (!guestfd_array) { > + /* New entries zero-initialized, i.e. type GuestFDUnused */ > + guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); > + } > + > + for (i = FIRST_GUEST_FD; i < guestfd_array->len; i++) { > + GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i); > + > + if (gf->type == GuestFDUnused) { > + return i; > + } > + } > + > + /* All elements already in use: expand the array */ > + g_array_set_size(guestfd_array, i + 1); > + return i; > +} > + > +/* > + * Look up the guestfd in the data structure; return NULL > + * for out of bounds, but don't check whether the slot is unused. > + * This is used internally by the other guestfd functions. > + */ > +static GuestFD *do_get_guestfd(int guestfd) > +{ > + if (!guestfd_array) { > + return NULL; > + } > + > + if (guestfd < 0 || guestfd >= guestfd_array->len) { > + return NULL; > + } > + > + return &g_array_index(guestfd_array, GuestFD, guestfd); > +} > + > +/* > + * Associate the specified guest fd (which must have been > + * allocated via alloc_fd() and not previously used) with > + * the specified host/gdb fd. > + */ > +static void associate_guestfd(int guestfd, int hostfd) > +{ > + GuestFD *gf = do_get_guestfd(guestfd); > + > + assert(gf); > + gf->type = use_gdb_syscalls() ? GuestFDGDB : GuestFDHost; > + gf->hostfd = hostfd; > +} > + > +/* > + * Deallocate the specified guest file descriptor. This doesn't > + * close the host fd, it merely undoes the work of alloc_fd(). > + */ > +static void dealloc_guestfd(int guestfd) > +{ > + GuestFD *gf = do_get_guestfd(guestfd); > + > + assert(gf); > + gf->type = GuestFDUnused; > +} > + > +/* > + * Given a guest file descriptor, get the associated struct. > + * If the fd is not valid, return NULL. This is the function > + * used by the various semihosting calls to validate a handle > + * from the guest. > + * Note: calling alloc_guestfd() or dealloc_guestfd() will > + * invalidate any GuestFD* obtained by calling this function. > + */ > +static GuestFD *get_guestfd(int guestfd) > +{ > + GuestFD *gf = do_get_guestfd(guestfd); > + > + if (!gf || gf->type == GuestFDUnused) { > + return NULL; > + } > + return gf; > +} > + > +/* > + * The semihosting API has no concept of its errno being thread-safe, > + * as the API design predates SMP CPUs and was intended as a simple > + * real-hardware set of debug functionality. For QEMU, we make the > + * errno be per-thread in linux-user mode; in softmmu it is a simple > + * global, and we assume that the guest takes care of avoiding any races. > + */ > +#ifndef CONFIG_USER_ONLY > +static target_ulong syscall_err; > + > +#include "exec/softmmu-semi.h" > +#endif > + > +static inline uint32_t set_swi_errno(CPURISCVState *env, uint32_t code) > +{ > + if (code == (uint32_t)-1) { > +#ifdef CONFIG_USER_ONLY > + CPUState *cs = env_cpu(env); > + TaskState *ts = cs->opaque; > + > + ts->swi_errno = errno; > +#else > + syscall_err = errno; > +#endif > + } > + return code; > +} > + > +static inline uint32_t get_swi_errno(CPURISCVState *env) > +{ > +#ifdef CONFIG_USER_ONLY > + CPUState *cs = env_cpu(env); > + TaskState *ts = cs->opaque; > + > + return ts->swi_errno; > +#else > + return syscall_err; > +#endif > +} > + > +static target_ulong riscv_semi_syscall_len; Could we split all of the shared code out somewhere? > + > +static void riscv_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) > +{ > + RISCVCPU *cpu = RISCV_CPU(cs); > + CPURISCVState *env = &cpu->env; > + target_ulong reg0 = env->gpr[xA0]; > + > + if (ret == (target_ulong)-1) { > + errno = err; > + set_swi_errno(env, -1); > + reg0 = ret; > + } else { > + /* Fixup syscalls that use nonstardard return conventions. */ > + switch (reg0) { > + case TARGET_SYS_WRITE: > + case TARGET_SYS_READ: > + reg0 = riscv_semi_syscall_len - ret; > + break; > + case TARGET_SYS_SEEK: > + reg0 = 0; > + break; > + default: > + reg0 = ret; > + break; > + } > + } > + env->gpr[xA0] = reg0; > +} > + > +static target_ulong riscv_flen_buf(RISCVCPU *cpu) > +{ > + /* > + * Return an address in target memory of 64 bytes where the remote > + * gdb should write its stat struct. (The format of this structure > + * is defined by GDB's remote protocol and is not target-specific.) > + * We put this on the guest's stack just below SP. > + */ > + CPURISCVState *env = &cpu->env; > + target_ulong sp; > + > + sp = env->gpr[xSP]; > + > + return sp - 64; > +} > + > +static void riscv_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong > err) > +{ > + RISCVCPU *cpu = RISCV_CPU(cs); > + CPURISCVState *env = &cpu->env; > + /* > + * The size is always stored in big-endian order, extract > + * the value. We assume the size always fit in 32 bits. > + */ > + uint32_t size; > + cpu_memory_rw_debug(cs, riscv_flen_buf(cpu) + 32, (uint8_t *)&size, 4, > 0); > + size = be32_to_cpu(size); > + env->gpr[xA0] = size; > + errno = err; > + set_swi_errno(env, -1); > +} > + > +static int riscv_semi_open_guestfd; > + > +static void riscv_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong > err) > +{ > + RISCVCPU *cpu = RISCV_CPU(cs); > + CPURISCVState *env = &cpu->env; > + if (ret == (target_ulong)-1) { > + errno = err; > + set_swi_errno(env, -1); > + dealloc_guestfd(riscv_semi_open_guestfd); > + } else { > + associate_guestfd(riscv_semi_open_guestfd, ret); > + ret = riscv_semi_open_guestfd; > + } > + > + env->gpr[xA0] = ret; > +} > + > +static target_ulong riscv_gdb_syscall(RISCVCPU *cpu, gdb_syscall_complete_cb > cb, > + const char *fmt, ...) > +{ > + va_list va; > + CPURISCVState *env = &cpu->env; > + > + va_start(va, fmt); > + gdb_do_syscallv(cb, fmt, va); > + va_end(va); > + > + /* > + * FIXME: in softmmu mode, the gdbstub will schedule our callback > + * to occur, but will not actually call it to complete the syscall > + * until after this function has returned and we are back in the > + * CPU main loop. Therefore callers to this function must not > + * do anything with its return value, because it is not necessarily > + * the result of the syscall, but could just be the old value of X0. > + * The only thing safe to do with this is that the callers of > + * do_riscv_semihosting() will write it straight back into X0. > + * (In linux-user mode, the callback will have happened before > + * gdb_do_syscallv() returns.) > + * > + * We should tidy this up so neither this function nor > + * do_riscv_semihosting() return a value, so the mistake of > + * doing something with the return value is not possible to make. > + */ > + > + return env->gpr[xA0]; > +} > + > +/* > + * Types for functions implementing various semihosting calls > + * for specific types of guest file descriptor. These must all > + * do the work and return the required return value for the guest, > + * setting the guest errno if appropriate. > + */ > +typedef uint32_t sys_closefn(RISCVCPU *cpu, GuestFD *gf); > +typedef uint32_t sys_writefn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len); > +typedef uint32_t sys_readfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len); > +typedef uint32_t sys_isattyfn(RISCVCPU *cpu, GuestFD *gf); > +typedef uint32_t sys_seekfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong offset); > +typedef uint32_t sys_flenfn(RISCVCPU *cpu, GuestFD *gf); > + > +static uint32_t host_closefn(RISCVCPU *cpu, GuestFD *gf) > +{ > + CPURISCVState *env = &cpu->env; > + > + return set_swi_errno(env, close(gf->hostfd)); > +} > + > +static uint32_t host_writefn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + uint32_t ret; > + CPURISCVState *env = &cpu->env; > + char *s = lock_user(VERIFY_READ, buf, len, 1); > + if (!s) { > + /* Return bytes not written on error */ > + return len; > + } > + ret = set_swi_errno(env, write(gf->hostfd, s, len)); > + unlock_user(s, buf, 0); > + if (ret == (uint32_t)-1) { > + ret = 0; > + } > + /* Return bytes not written */ > + return len - ret; > +} > + > +static uint32_t host_readfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + uint32_t ret; > + CPURISCVState *env = &cpu->env; > + char *s = lock_user(VERIFY_WRITE, buf, len, 0); > + if (!s) { > + /* return bytes not read */ > + return len; > + } > + do { > + ret = set_swi_errno(env, read(gf->hostfd, s, len)); > + } while (ret == -1 && errno == EINTR); > + unlock_user(s, buf, len); > + if (ret == (uint32_t)-1) { > + ret = 0; > + } > + /* Return bytes not read */ > + return len - ret; > +} > + > +static uint32_t host_isattyfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return isatty(gf->hostfd); > +} > + > +static uint32_t host_seekfn(RISCVCPU *cpu, GuestFD *gf, target_ulong offset) > +{ > + CPURISCVState *env = &cpu->env; > + uint32_t ret = set_swi_errno(env, lseek(gf->hostfd, offset, SEEK_SET)); > + if (ret == (uint32_t)-1) { > + return -1; > + } > + return 0; > +} > + > +static uint32_t host_flenfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + CPURISCVState *env = &cpu->env; > + struct stat buf; > + uint32_t ret = set_swi_errno(env, fstat(gf->hostfd, &buf)); > + if (ret == (uint32_t)-1) { > + return -1; > + } > + return buf.st_size; > +} > + > +static uint32_t gdb_closefn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "close,%x", gf->hostfd); > +} > + > +static uint32_t gdb_writefn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + riscv_semi_syscall_len = len; > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "write,%x,%x,%x", > + gf->hostfd, buf, len); > +} > + > +static uint32_t gdb_readfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + riscv_semi_syscall_len = len; > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "read,%x,%x,%x", > + gf->hostfd, buf, len); > +} > + > +static uint32_t gdb_isattyfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "isatty,%x", gf->hostfd); > +} > + > +static uint32_t gdb_seekfn(RISCVCPU *cpu, GuestFD *gf, target_ulong offset) > +{ > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "lseek,%x,%x,0", > + gf->hostfd, offset); > +} > + > +static uint32_t gdb_flenfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return riscv_gdb_syscall(cpu, riscv_semi_flen_cb, "fstat,%x,%x", > + gf->hostfd, riscv_flen_buf(cpu)); > +} > + > +#define SHFB_MAGIC_0 0x53 > +#define SHFB_MAGIC_1 0x48 > +#define SHFB_MAGIC_2 0x46 > +#define SHFB_MAGIC_3 0x42 > + > +/* Feature bits reportable in feature byte 0 */ > +#define SH_EXT_EXIT_EXTENDED (1 << 0) > +#define SH_EXT_STDOUT_STDERR (1 << 1) > + > +static const uint8_t featurefile_data[] = { > + SHFB_MAGIC_0, > + SHFB_MAGIC_1, > + SHFB_MAGIC_2, > + SHFB_MAGIC_3, > + SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ > +}; > + > +static void init_featurefile_guestfd(int guestfd) > +{ > + GuestFD *gf = do_get_guestfd(guestfd); > + > + assert(gf); > + gf->type = GuestFDFeatureFile; > + gf->featurefile_offset = 0; > +} > + > +static uint32_t featurefile_closefn(RISCVCPU *cpu, GuestFD *gf) > +{ > + /* Nothing to do */ > + return 0; > +} > + > +static uint32_t featurefile_writefn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + /* This fd can never be open for writing */ > + CPURISCVState *env = &cpu->env; > + > + errno = EBADF; > + return set_swi_errno(env, -1); > +} > + > +static uint32_t featurefile_readfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong buf, uint32_t len) > +{ > + uint32_t i; > +#ifndef CONFIG_USER_ONLY > + CPURISCVState *env = &cpu->env; > +#endif > + char *s; > + > + s = lock_user(VERIFY_WRITE, buf, len, 0); > + if (!s) { > + return len; > + } > + > + for (i = 0; i < len; i++) { > + if (gf->featurefile_offset >= sizeof(featurefile_data)) { > + break; > + } > + s[i] = featurefile_data[gf->featurefile_offset]; > + gf->featurefile_offset++; > + } > + > + unlock_user(s, buf, len); > + > + /* Return number of bytes not read */ > + return len - i; > +} > + > +static uint32_t featurefile_isattyfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return 0; > +} > + > +static uint32_t featurefile_seekfn(RISCVCPU *cpu, GuestFD *gf, > + target_ulong offset) > +{ > + gf->featurefile_offset = offset; > + return 0; > +} > + > +static uint32_t featurefile_flenfn(RISCVCPU *cpu, GuestFD *gf) > +{ > + return sizeof(featurefile_data); > +} > + > +typedef struct GuestFDFunctions { > + sys_closefn *closefn; > + sys_writefn *writefn; > + sys_readfn *readfn; > + sys_isattyfn *isattyfn; > + sys_seekfn *seekfn; > + sys_flenfn *flenfn; > +} GuestFDFunctions; > + > +static const GuestFDFunctions guestfd_fns[] = { > + [GuestFDHost] = { > + .closefn = host_closefn, > + .writefn = host_writefn, > + .readfn = host_readfn, > + .isattyfn = host_isattyfn, > + .seekfn = host_seekfn, > + .flenfn = host_flenfn, > + }, > + [GuestFDGDB] = { > + .closefn = gdb_closefn, > + .writefn = gdb_writefn, > + .readfn = gdb_readfn, > + .isattyfn = gdb_isattyfn, > + .seekfn = gdb_seekfn, > + .flenfn = gdb_flenfn, > + }, > + [GuestFDFeatureFile] = { > + .closefn = featurefile_closefn, > + .writefn = featurefile_writefn, > + .readfn = featurefile_readfn, > + .isattyfn = featurefile_isattyfn, > + .seekfn = featurefile_seekfn, > + .flenfn = featurefile_flenfn, > + }, > +}; > + > +/* > + * Read the input value from the argument block; fail the semihosting > + * call if the memory read fails. > + */ > +#define GET_ARG(n) do { \ > + if (get_user_ual(arg ## n, args + (n) * sizeof(target_ulong))) { \ > + errno = EFAULT; \ > + return set_swi_errno(env, -1); \ > + } \ > + } while (0) > + > +#define SET_ARG(n, val) \ > + put_user_ual(val, args + (n) * sizeof(target_ulong)) > + > +/* > + * Do a semihosting call. > + * > + * The specification always says that the "return register" either > + * returns a specific value or is corrupted, so we don't need to > + * report to our caller whether we are returning a value or trying to > + * leave the register unchanged. We use 0xdeadbeef as the return value > + * when there isn't a defined return value for the call. > + */ > +target_ulong do_riscv_semihosting(CPURISCVState *env) > +{ > + RISCVCPU *cpu = env_archcpu(env); > + CPUState *cs = env_cpu(env); > + target_ulong args; > + target_ulong arg0, arg1, arg2, arg3; > + char *s; > + int nr; > + uint32_t ret; > + uint32_t len; > + GuestFD *gf; > + > + nr = env->gpr[xA0] & 0xffffffffU; > + args = env->gpr[xA1]; > + > + switch (nr) { > + case TARGET_SYS_OPEN: > + { > + int guestfd; > + > + GET_ARG(0); > + GET_ARG(1); > + GET_ARG(2); > + s = lock_user_string(arg0); > + if (!s) { > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + if (arg1 >= 12) { > + unlock_user(s, arg0, 0); > + errno = EINVAL; > + return set_swi_errno(env, -1); > + } > + > + guestfd = alloc_guestfd(); > + if (guestfd < 0) { > + unlock_user(s, arg0, 0); > + errno = EMFILE; > + return set_swi_errno(env, -1); > + } > + > + if (strcmp(s, ":tt") == 0) { > + int result_fileno; > + > + /* > + * We implement SH_EXT_STDOUT_STDERR, so: > + * open for read == stdin > + * open for write == stdout > + * open for append == stderr > + */ > + if (arg1 < 4) { > + result_fileno = STDIN_FILENO; > + } else if (arg1 < 8) { > + result_fileno = STDOUT_FILENO; > + } else { > + result_fileno = STDERR_FILENO; > + } > + associate_guestfd(guestfd, result_fileno); > + unlock_user(s, arg0, 0); > + return guestfd; > + } > + if (strcmp(s, ":semihosting-features") == 0) { > + unlock_user(s, arg0, 0); > + /* We must fail opens for modes other than 0 ('r') or 1 ('rb') */ > + if (arg1 != 0 && arg1 != 1) { > + dealloc_guestfd(guestfd); > + errno = EACCES; > + return set_swi_errno(env, -1); > + } > + init_featurefile_guestfd(guestfd); > + return guestfd; > + } > + > + if (use_gdb_syscalls()) { > + riscv_semi_open_guestfd = guestfd; > + ret = riscv_gdb_syscall(cpu, riscv_semi_open_cb, > + "open,%s,%x,1a4", arg0, > + (int)arg2 + 1, gdb_open_modeflags[arg1]); > + } else { > + ret = set_swi_errno(env, open(s, open_modeflags[arg1], 0644)); > + if (ret == (uint32_t)-1) { > + dealloc_guestfd(guestfd); > + } else { > + associate_guestfd(guestfd, ret); > + ret = guestfd; > + } > + } > + unlock_user(s, arg0, 0); > + return ret; > + } > + case TARGET_SYS_CLOSE: > + GET_ARG(0); > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + ret = guestfd_fns[gf->type].closefn(cpu, gf); > + dealloc_guestfd(arg0); > + return ret; > + case TARGET_SYS_WRITEC: > + qemu_semihosting_console_outc(env, args); > + return 0xdeadbeef; > + case TARGET_SYS_WRITE0: > + return qemu_semihosting_console_outs(env, args); > + case TARGET_SYS_WRITE: > + GET_ARG(0); > + GET_ARG(1); > + GET_ARG(2); > + len = arg2; > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + return guestfd_fns[gf->type].writefn(cpu, gf, arg1, len); > + case TARGET_SYS_READ: > + GET_ARG(0); > + GET_ARG(1); > + GET_ARG(2); > + len = arg2; > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + return guestfd_fns[gf->type].readfn(cpu, gf, arg1, len); > + case TARGET_SYS_READC: > + return qemu_semihosting_console_inc(env); > + case TARGET_SYS_ISTTY: > + GET_ARG(0); > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + return guestfd_fns[gf->type].isattyfn(cpu, gf); > + case TARGET_SYS_SEEK: > + GET_ARG(0); > + GET_ARG(1); > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + return guestfd_fns[gf->type].seekfn(cpu, gf, arg1); > + case TARGET_SYS_FLEN: > + GET_ARG(0); > + > + gf = get_guestfd(arg0); > + if (!gf) { > + errno = EBADF; > + return set_swi_errno(env, -1); > + } > + > + return guestfd_fns[gf->type].flenfn(cpu, gf); > + case TARGET_SYS_TMPNAM: > + qemu_log_mask(LOG_UNIMP, "%s: SYS_TMPNAM not implemented", __func__); > + return -1; > + case TARGET_SYS_REMOVE: > + GET_ARG(0); > + GET_ARG(1); > + if (use_gdb_syscalls()) { > + ret = riscv_gdb_syscall(cpu, riscv_semi_cb, "unlink,%s", > + arg0, (int)arg1 + 1); > + } else { > + s = lock_user_string(arg0); > + if (!s) { > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + ret = set_swi_errno(env, remove(s)); > + unlock_user(s, arg0, 0); > + } > + return ret; > + case TARGET_SYS_RENAME: > + GET_ARG(0); > + GET_ARG(1); > + GET_ARG(2); > + GET_ARG(3); > + if (use_gdb_syscalls()) { > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "rename,%s,%s", > + arg0, (int)arg1 + 1, arg2, (int)arg3 + 1); > + } else { > + char *s2; > + s = lock_user_string(arg0); > + s2 = lock_user_string(arg2); > + if (!s || !s2) { > + errno = EFAULT; > + ret = set_swi_errno(env, -1); > + } else { > + ret = set_swi_errno(env, rename(s, s2)); > + } > + if (s2) { > + unlock_user(s2, arg2, 0); > + } > + if (s) { > + unlock_user(s, arg0, 0); > + } > + return ret; > + } > + case TARGET_SYS_CLOCK: > + return clock() / (CLOCKS_PER_SEC / 100); > + case TARGET_SYS_TIME: > + return set_swi_errno(env, time(NULL)); > + case TARGET_SYS_SYSTEM: > + GET_ARG(0); > + GET_ARG(1); > + if (use_gdb_syscalls()) { > + return riscv_gdb_syscall(cpu, riscv_semi_cb, "system,%s", > + arg0, (int)arg1 + 1); > + } else { > + s = lock_user_string(arg0); > + if (!s) { > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + ret = set_swi_errno(env, system(s)); > + unlock_user(s, arg0, 0); > + return ret; > + } > + case TARGET_SYS_ERRNO: > + return get_swi_errno(env); > + case TARGET_SYS_GET_CMDLINE: > + { > + /* > + * Build a command-line from the original argv. > + * > + * The inputs are: > + * * arg0, pointer to a buffer of at least the size > + * specified in arg1. > + * * arg1, size of the buffer pointed to by arg0 in > + * bytes. > + * > + * The outputs are: > + * * arg0, pointer to null-terminated string of the > + * command line. > + * * arg1, length of the string pointed to by arg0. > + */ > + > + char *output_buffer; > + size_t input_size; > + size_t output_size; > + int status = 0; > +#if !defined(CONFIG_USER_ONLY) > + const char *cmdline; > +#else > + TaskState *ts = cs->opaque; > +#endif > + GET_ARG(0); > + GET_ARG(1); > + input_size = arg1; > + /* Compute the size of the output string. */ > +#if !defined(CONFIG_USER_ONLY) > + cmdline = semihosting_get_cmdline(); > + if (cmdline == NULL) { > + cmdline = ""; /* Default to an empty line. */ > + } > + output_size = strlen(cmdline) + 1; /* Count terminating 0. */ > +#else > + unsigned int i; > + > + output_size = ts->info->arg_end - ts->info->arg_start; > + if (!output_size) { > + /* > + * We special-case the "empty command line" case (argc==0). > + * Just provide the terminating 0. > + */ > + output_size = 1; > + } > +#endif > + > + if (output_size > input_size) { > + /* Not enough space to store command-line arguments. */ > + errno = E2BIG; > + return set_swi_errno(env, -1); > + } > + > + /* Adjust the command-line length. */ > + if (SET_ARG(1, output_size - 1)) { > + /* Couldn't write back to argument block */ > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + > + /* Lock the buffer on the RISC-V side. */ > + output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0); > + if (!output_buffer) { > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + > + /* Copy the command-line arguments. */ > +#if !defined(CONFIG_USER_ONLY) > + pstrcpy(output_buffer, output_size, cmdline); > +#else > + if (output_size == 1) { > + /* Empty command-line. */ > + output_buffer[0] = '\0'; > + goto out; > + } > + > + if (copy_from_user(output_buffer, ts->info->arg_start, > + output_size)) { > + errno = EFAULT; > + status = set_swi_errno(env, -1); > + goto out; > + } > + > + /* Separate arguments by white spaces. */ > + for (i = 0; i < output_size - 1; i++) { > + if (output_buffer[i] == 0) { > + output_buffer[i] = ' '; > + } > + } > + out: > +#endif > + /* Unlock the buffer on the RISC-V side. */ > + unlock_user(output_buffer, arg0, output_size); > + > + return status; > + } > + case TARGET_SYS_HEAPINFO: > + { > + target_ulong retvals[4]; > + target_ulong limit; > + int i; > +#ifdef CONFIG_USER_ONLY > + TaskState *ts = cs->opaque; > +#endif > + > + GET_ARG(0); > + > +#ifdef CONFIG_USER_ONLY > + /* > + * Some C libraries assume the heap immediately follows .bss, so > + * allocate it using sbrk. > + */ > + if (!ts->heap_limit) { > + abi_ulong ret; > + > + ts->heap_base = do_brk(0); > + limit = ts->heap_base + RISCV_HEAP_SIZE; > + /* Try a big heap, and reduce the size if that fails. */ > + for (;;) { > + ret = do_brk(limit); > + if (ret >= limit) { > + break; > + } > + limit = (ts->heap_base >> 1) + (limit >> 1); > + } > + ts->heap_limit = limit; > + } > + > + retvals[0] = ts->heap_base; > + retvals[1] = ts->heap_limit; > + retvals[2] = ts->stack_base; > + retvals[3] = 0; /* Stack limit. */ > +#else > + limit = ram_size; > + /* TODO: Make this use the limit of the loaded application. */ > + retvals[0] = limit / 2; > + retvals[1] = limit; > + retvals[2] = limit; /* Stack base */ > + retvals[3] = 0; /* Stack limit. */ > +#endif > + > + for (i = 0; i < ARRAY_SIZE(retvals); i++) { > + bool fail; > + > + fail = put_user_ual(retvals[i], > + arg0 + i * sizeof(target_ulong)); > + > + if (fail) { > + /* Couldn't write back to argument block */ > + errno = EFAULT; > + return set_swi_errno(env, -1); > + } > + } > + return 0; > + } > + case TARGET_SYS_EXIT: > + case TARGET_SYS_EXIT_EXTENDED: > + if (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 0) { > + /* > + * The A64 version of SYS_EXIT takes a parameter block, > + * so the application-exit type can return a subcode which > + * is the exit status code from the application. > + * SYS_EXIT_EXTENDED is an a new-in-v2.0 optional function > + * which allows A32/T32 guests to also provide a status code. > + */ > + GET_ARG(0); > + GET_ARG(1); > + > + if (arg0 == ADP_Stopped_ApplicationExit) { > + ret = arg1; > + } else { > + ret = 1; > + } > + } else { > + /* > + * The A32/T32 version of SYS_EXIT specifies only > + * Stopped_ApplicationExit as normal exit, but does not > + * allow the guest to specify the exit status code. > + * Everything else is considered an error. > + */ > + ret = (args == ADP_Stopped_ApplicationExit) ? 0 : 1; > + } > + gdb_exit(env, ret); > + exit(ret); > + case TARGET_SYS_SYNCCACHE: > + /* > + * Clean the D-cache and invalidate the I-cache for the specified > + * virtual address range. This is a nop for us since we don't > + * implement caches. This is only present on A64. > + */ > + if (sizeof(target_ulong) == 8) { > + return 0; > + } > + /* fall through -- invalid for A32/T32 */ > + default: > + fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); > + cpu_dump_state(cs, stderr, 0); > + abort(); > + } > +} > diff --git a/target/riscv/translate.c b/target/riscv/translate.c > index 79dca2291b..ecf594babc 100644 > --- a/target/riscv/translate.c > +++ b/target/riscv/translate.c > @@ -63,6 +63,7 @@ typedef struct DisasContext { > uint16_t vlen; > uint16_t mlen; > bool vl_eq_vlmax; > + CPUState *cs; I'm not sure we should do this. > } DisasContext; > > #ifdef TARGET_RISCV64 > @@ -746,6 +747,15 @@ static bool gen_shift(DisasContext *ctx, arg_r *a, > return true; > } > > +static uint32_t opcode_at(DisasContextBase *dcbase, target_ulong pc) > +{ > + DisasContext *ctx = container_of(dcbase, DisasContext, base); > + CPUState *cpu = ctx->cs; > + CPURISCVState *env = cpu->env_ptr; > + > + return cpu_ldl_code(env, pc); @Richard Henderson is this ok? Alistair > +} > + > /* Include insn module translation function */ > #include "insn_trans/trans_rvi.c.inc" > #include "insn_trans/trans_rvm.c.inc" > @@ -812,6 +822,7 @@ static void riscv_tr_init_disas_context(DisasContextBase > *dcbase, CPUState *cs) > ctx->lmul = FIELD_EX32(tb_flags, TB_FLAGS, LMUL); > ctx->mlen = 1 << (ctx->sew + 3 - ctx->lmul); > ctx->vl_eq_vlmax = FIELD_EX32(tb_flags, TB_FLAGS, VL_EQ_VLMAX); > + ctx->cs = cs; > } > > static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu) > -- > 2.28.0 > > >