diff mbox series

riscv: Add semihosting support [v3]

Message ID 20200128231840.508986-1-keithp@keithp.com (mailing list archive)
State New, archived
Headers show
Series riscv: Add semihosting support [v3] | expand

Commit Message

Denis V. Lunev" via Jan. 28, 2020, 11:18 p.m. UTC
Adapt the arm semihosting support code for RISCV. This implementation
is based on the standard for RISC-V semihosting as documented in

	https://riscv.org/specifications/

Signed-off-by: Keith Packard <keithp@keithp.com>

---

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.
---
 default-configs/riscv32-softmmu.mak           |    1 +
 linux-user/qemu.h                             |    2 +
 qemu-options.hx                               |    4 +-
 target/riscv/Makefile.objs                    |    2 +-
 target/riscv/cpu.h                            |    2 +
 target/riscv/cpu_bits.h                       |    1 +
 target/riscv/cpu_helper.c                     |    9 +
 .../riscv/insn_trans/trans_privileged.inc.c   |   23 +-
 target/riscv/riscv-semi.c                     | 1073 +++++++++++++++++
 target/riscv/translate.c                      |   11 +
 10 files changed, 1124 insertions(+), 4 deletions(-)
 create mode 100644 target/riscv/riscv-semi.c

Comments

Denis V. Lunev" via Jan. 28, 2020, 11:34 p.m. UTC | #1
Keith Packard <keithp@keithp.com> writes:

> Adapt the arm semihosting support code for RISCV. This implementation
> is based on the standard for RISC-V semihosting as documented in

Sorry for the noise on the list; I failed to run checkpatch.pl before
submitting. I've re-submittd [v4] with the formatting errors corrected.
no-reply@patchew.org Jan. 28, 2020, 11:46 p.m. UTC | #2
Patchew URL: https://patchew.org/QEMU/20200128231840.508986-1-keithp@keithp.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20200128231840.508986-1-keithp@keithp.com
Subject: [PATCH] riscv: Add semihosting support [v3]

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 - [tag update]      patchew/20200128233216.515171-1-keithp@keithp.com -> patchew/20200128233216.515171-1-keithp@keithp.com
Switched to a new branch 'test'
a3ca2be riscv: Add semihosting support [v3]

=== OUTPUT BEGIN ===
WARNING: Block comments use a leading /* on a separate line
#131: FILE: target/riscv/insn_trans/trans_privileged.inc.c:36:
+    /* The RISC-V semihosting spec specifies the following

WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#153: 
new file mode 100644

WARNING: Block comments use a leading /* on a separate line
#223: FILE: target/riscv/riscv-semi.c:66:
+/* ADP_Stopped_ApplicationExit is used for exit(0),

WARNING: Block comments use a trailing */ on a separate line
#224: FILE: target/riscv/riscv-semi.c:67:
+ * anything else is implemented as exit(1) */

WARNING: Block comments use a leading /* on a separate line
#453: FILE: target/riscv/riscv-semi.c:296:
+    /* Return an address in target memory of 64 bytes where the remote

WARNING: Block comments use a leading /* on a separate line
#470: FILE: target/riscv/riscv-semi.c:313:
+    /* The size is always stored in big-endian order, extract

WARNING: Block comments use * on subsequent lines
#471: FILE: target/riscv/riscv-semi.c:314:
+    /* The size is always stored in big-endian order, extract
+       the value. We assume the size always fit in 32 bits.  */

WARNING: Block comments use a trailing */ on a separate line
#471: FILE: target/riscv/riscv-semi.c:314:
+       the value. We assume the size always fit in 32 bits.  */

WARNING: Block comments use a leading /* on a separate line
#778: FILE: target/riscv/riscv-semi.c:621:
+/* Read the input value from the argument block; fail the semihosting

ERROR: "foo * bar" should be "foo *bar"
#806: FILE: target/riscv/riscv-semi.c:649:
+    char * s;

WARNING: line over 80 characters
#875: FILE: target/riscv/riscv-semi.c:718:
+            ret = riscv_gdb_syscall(cpu, riscv_semi_open_cb, "open,%s,%x,1a4", arg0,

ERROR: spaces required around that '+' (ctx:VxV)
#876: FILE: target/riscv/riscv-semi.c:719:
+                                  (int)arg2+1, gdb_open_modeflags[arg1]);
                                            ^

ERROR: spaces required around that '+' (ctx:VxV)
#973: FILE: target/riscv/riscv-semi.c:816:
+                                  arg0, (int)arg1+1);
                                                  ^

ERROR: spaces required around that '+' (ctx:VxV)
#991: FILE: target/riscv/riscv-semi.c:834:
+                                   arg0, (int)arg1+1, arg2, (int)arg3+1);
                                                   ^

ERROR: spaces required around that '+' (ctx:VxV)
#991: FILE: target/riscv/riscv-semi.c:834:
+                                   arg0, (int)arg1+1, arg2, (int)arg3+1);
                                                                      ^

ERROR: braces {} are necessary for all arms of this statement
#1002: FILE: target/riscv/riscv-semi.c:845:
+            if (s2)
[...]

ERROR: braces {} are necessary for all arms of this statement
#1004: FILE: target/riscv/riscv-semi.c:847:
+            if (s)
[...]

ERROR: spaces required around that '+' (ctx:VxV)
#1017: FILE: target/riscv/riscv-semi.c:860:
+                                   arg0, (int)arg1+1);
                                                   ^

WARNING: Block comments use a leading /* on a separate line
#1032: FILE: target/riscv/riscv-semi.c:875:
+            /* Build a command-line from the original argv.

WARNING: line over 80 characters
#1176: FILE: target/riscv/riscv-semi.c:1019:
+                fail = put_user_ual(retvals[i], arg0 + i * sizeof(target_ulong));

total: 8 errors, 12 warnings, 1197 lines checked

Commit a3ca2be73efc (riscv: Add semihosting support [v3]) has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20200128231840.508986-1-keithp@keithp.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
diff mbox series

Patch

diff --git a/default-configs/riscv32-softmmu.mak b/default-configs/riscv32-softmmu.mak
index 1ae077ed87..21b7bedf19 100644
--- a/default-configs/riscv32-softmmu.mak
+++ b/default-configs/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/linux-user/qemu.h b/linux-user/qemu.h
index 560a68090e..122d95d6b6 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)
diff --git a/qemu-options.hx b/qemu-options.hx
index 224a8e8712..4892e6b12c 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4109,7 +4109,7 @@  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_MIPS | QEMU_ARCH_NIOS2)
+    QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV)
 STEXI
 @item -semihosting
 @findex -semihosting
@@ -4119,7 +4119,7 @@  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)
 STEXI
 @item -semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]
 @findex -semihosting-config
diff --git a/target/riscv/Makefile.objs b/target/riscv/Makefile.objs
index ff651f69f6..6fd7f40e29 100644
--- a/target/riscv/Makefile.objs
+++ b/target/riscv/Makefile.objs
@@ -1,4 +1,4 @@ 
-obj-y += translate.o op_helper.o cpu_helper.o cpu.o csr.o fpu_helper.o gdbstub.o
+obj-y += translate.o op_helper.o cpu_helper.o cpu.o csr.o fpu_helper.o gdbstub.o riscv-semi.o
 obj-$(CONFIG_SOFTMMU) += pmp.o
 
 ifeq ($(CONFIG_SOFTMMU),y)
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index de0a8d893a..310ec6ab40 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -338,6 +338,8 @@  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"
 
 #endif /* RISCV_CPU_H */
diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
index e99834856c..c45745d73b 100644
--- a/target/riscv/cpu_bits.h
+++ b/target/riscv/cpu_bits.h
@@ -496,6 +496,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
 
 #define RISCV_EXCP_INT_FLAG                0x80000000
 #define RISCV_EXCP_INT_MASK                0x7fffffff
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 85403da9c8..6a04469936 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -532,6 +532,15 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         [PRV_M] = RISCV_EXCP_M_ECALL
     };
 
+    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.inc.c b/target/riscv/insn_trans/trans_privileged.inc.c
index c5e4b3e49a..ddc26e889f 100644
--- a/target/riscv/insn_trans/trans_privileged.inc.c
+++ b/target/riscv/insn_trans/trans_privileged.inc.c
@@ -29,7 +29,28 @@  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/riscv-semi.c b/target/riscv/riscv-semi.c
new file mode 100644
index 0000000000..60d67a476e
--- /dev/null
+++ b/target/riscv/riscv-semi.c
@@ -0,0 +1,1073 @@ 
+/*
+ *  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
+ */
+
+#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;
+
+/*
+ * 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 = 0; 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;
+
+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 14dc71156b..4ee0d75f86 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -55,6 +55,7 @@  typedef struct DisasContext {
        to reset this known value.  */
     int frm;
     bool ext_ifencei;
+    CPUState *cs;
 } DisasContext;
 
 #ifdef TARGET_RISCV64
@@ -698,6 +699,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);
+}
+
 /* Include insn module translation function */
 #include "insn_trans/trans_rvi.inc.c"
 #include "insn_trans/trans_rvm.inc.c"
@@ -743,6 +753,7 @@  static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
     ctx->misa = env->misa;
     ctx->frm = -1;  /* unknown rounding mode */
     ctx->ext_ifencei = cpu->cfg.ext_ifencei;
+    ctx->cs = cs;
 }
 
 static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)