diff mbox series

riscv: Add semihosting support [v8]

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

Commit Message

Keith Packard Oct. 23, 2020, 9:49 p.m. UTC
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 <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.

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

Comments

Alistair Francis Oct. 24, 2020, 12:22 a.m. UTC | #1
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 <keithp@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 <palmer@dabbelt.com>
>  M: Alistair Francis <Alistair.Francis@wdc.com>
>  M: Sagar Karandikar <sagark@eecs.berkeley.edu>
>  M: Bastian Koppelmann <kbastian@mail.uni-paderborn.de>
> +M: Keith Packard <keithp@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-riscv@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
>
>
>
Zhijian Li (Fujitsu)" via Oct. 24, 2020, 5:56 a.m. UTC | #2
Alistair Francis <alistair23@gmail.com> writes:

Thanks much for taking time to review this patch in detail. I've left
the indicated changes in a new version of my riscv-semihost branch here:

        https://github.com/keith-packard/qemu/tree/riscv-semihost

I'll post a new version once we've wound up discussion on the remaining
issues.

>> +M: Keith Packard <keithp@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.

Cool, I'm glad to not be listed. checkpatch.pl suggested that I might
need to add something here, so I went ahead and included it in case it
was necessary. (I probably should do some patch review though; SiFive is
rather dependent on QEMU continuing to be a great RISC-V emulator)

>> +#include "exec/cpu-all.h"
>
> This isn't used in the header so it shouldn't be here.

Worse than that -- it's already included in this file. I suspect
this is left over from a previous version and have removed it.

>> +#define RISCV_EXCP_SEMIHOST                      0x10
>
> I don't see this in the RISC-V spec, it seems to just be reserved, not
> for semihosting.

Hrm. It's entirely an internal implementation detail in QEMU and matches
how semihosting works in the ARM implementation -- the presence of the
semihosting breakpoint raises this exception which is then handled in
the usual exception processing path.

If there is ever a real exception that uses this number, we can
re-define this to something else. Or if you have a favorite number you'd
like to use instead, that'd be great.

>> + *  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.

Good suggestion. Fixed.

> Could we split all of the shared code out somewhere?

Yes, that seems like a reasonable suggestion. I haven't done so because
that brings a lot of additional obligations on the patch to not impact
the ARM implementation, and means that future changes to either the
RISC-V or ARM specifications would need to be careful to not impact the
other architecture as the code is modified.

Benjamin Herrenschmidt started a thread back in January about creating a
common semihosting implementation to be shared across ARM, RISC-V and
PPC. I'm not sure he ever published the resulting code, but we can
probably get whatever he's done and see if we want to go that way. I
suspect the biggest impact will be to the ARM maintainers who will end
up on the hook for reviewing the code to make sure it doesn't break
anything for them.

I can expand the semihost testing which picolibc currently performs
under QEMU on ARM, AARCH64 and RISC-V; that might help catch regressions
caused by this rework.

>> --- 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.

Yeah, the RISC-V semihosting requirement that three instructions be
compared to determine a valid 'sequence' is the least pleasing part of
the specification. This is the second version of this particular piece
of code.

We also changed the semihosting specification to require that all three
instructions lie on the same page to make sure they are all available if
any are available. In the application implementation, all that was
required to meet that was to put the sequence in a function and align
that to a 16-byte boundary as the function consists of four 32-bit
instructions:

        .global sys_semihost
        .balign 16
        .option push
        .option norvc
sys_semihost:
        slli zero, zero, 0x1f
        ebreak
        srai zero, zero, 0x7
        ret
        .option pop

>> +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?

Let me know if you've got a better plan, or even some suggestions on how
it might be improved as it seems like it a layering violation to me.
Alistair Francis Oct. 24, 2020, 3 p.m. UTC | #3
On Fri, Oct 23, 2020 at 10:56 PM Keith Packard <keithp@keithp.com> wrote:
>
> Alistair Francis <alistair23@gmail.com> writes:
>
> Thanks much for taking time to review this patch in detail. I've left
> the indicated changes in a new version of my riscv-semihost branch here:
>
>         https://github.com/keith-packard/qemu/tree/riscv-semihost
>
> I'll post a new version once we've wound up discussion on the remaining
> issues.
>
> >> +M: Keith Packard <keithp@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.
>
> Cool, I'm glad to not be listed. checkpatch.pl suggested that I might
> need to add something here, so I went ahead and included it in case it
> was necessary. (I probably should do some patch review though; SiFive is
> rather dependent on QEMU continuing to be a great RISC-V emulator)

That would be helpful, we are always short on reviewers.

>
> >> +#include "exec/cpu-all.h"
> >
> > This isn't used in the header so it shouldn't be here.
>
> Worse than that -- it's already included in this file. I suspect
> this is left over from a previous version and have removed it.
>
> >> +#define RISCV_EXCP_SEMIHOST                      0x10
> >
> > I don't see this in the RISC-V spec, it seems to just be reserved, not
> > for semihosting.
>
> Hrm. It's entirely an internal implementation detail in QEMU and matches
> how semihosting works in the ARM implementation -- the presence of the
> semihosting breakpoint raises this exception which is then handled in
> the usual exception processing path.

It's not fully internal though. Someone running with the `-d int`
command line argument will see these exceptions, which don't
correspond to anything in the spec.

Is there some way we could at least convey that information to users?

>
> If there is ever a real exception that uses this number, we can
> re-define this to something else. Or if you have a favorite number you'd
> like to use instead, that'd be great.

I think it would at least be better to use a high reserved number, but
like I mentioned above this is somewhat user visible.

>
> >> + *  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.
>
> Good suggestion. Fixed.
>
> > Could we split all of the shared code out somewhere?
>
> Yes, that seems like a reasonable suggestion. I haven't done so because
> that brings a lot of additional obligations on the patch to not impact
> the ARM implementation, and means that future changes to either the
> RISC-V or ARM specifications would need to be careful to not impact the
> other architecture as the code is modified.

That makes sense. If they start to diverge we can also re-split them out though.

>
> Benjamin Herrenschmidt started a thread back in January about creating a
> common semihosting implementation to be shared across ARM, RISC-V and
> PPC. I'm not sure he ever published the resulting code, but we can
> probably get whatever he's done and see if we want to go that way. I
> suspect the biggest impact will be to the ARM maintainers who will end
> up on the hook for reviewing the code to make sure it doesn't break
> anything for them.

AFAIK that unfortunately never progressed too far.

>
> I can expand the semihost testing which picolibc currently performs
> under QEMU on ARM, AARCH64 and RISC-V; that might help catch regressions
> caused by this rework.

That would be helpful.

When doing that make sure to split the patches up (this one is already
a little big) so that the ARM people can just review 1 patch.

>
> >> --- 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.
>
> Yeah, the RISC-V semihosting requirement that three instructions be
> compared to determine a valid 'sequence' is the least pleasing part of
> the specification. This is the second version of this particular piece
> of code.
>
> We also changed the semihosting specification to require that all three
> instructions lie on the same page to make sure they are all available if
> any are available. In the application implementation, all that was

I saw that, that is good news.

> required to meet that was to put the sequence in a function and align
> that to a 16-byte boundary as the function consists of four 32-bit
> instructions:
>
>         .global sys_semihost
>         .balign 16
>         .option push
>         .option norvc
> sys_semihost:
>         slli zero, zero, 0x1f
>         ebreak
>         srai zero, zero, 0x7
>         ret
>         .option pop
>
> >> +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?
>
> Let me know if you've got a better plan, or even some suggestions on how
> it might be improved as it seems like it a layering violation to me.

I don't have an idea off the top of my head, hopefully Richard already
knows the answer here :)

Alistair

>
> --
> -keith
Richard Henderson Oct. 26, 2020, 7:22 p.m. UTC | #4
On 10/23/20 2:49 PM, Keith Packard via wrote:
>  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);

Alistair asked if this approach is ok.  I think it is.  There are other places
in which we scan forward (usually only forward, not backward, but this is a
special nop, so it doesn't matter).

However:

(1) No need to re-read the current ebreak insn.  That is how we arrived here,
after all.

(2) You need to check for page boundaries before reading pre and post.
Otherwise you could wind up with SIGSEGV (or the equivalent internal qemu
exception) when you shouldn't.


r~
Richard Henderson Oct. 26, 2020, 7:26 p.m. UTC | #5
On 10/24/20 8:00 AM, Alistair Francis wrote:
>>>> +#define RISCV_EXCP_SEMIHOST                      0x10
>>>
>>> I don't see this in the RISC-V spec, it seems to just be reserved, not
>>> for semihosting.
>>
>> Hrm. It's entirely an internal implementation detail in QEMU and matches
>> how semihosting works in the ARM implementation -- the presence of the
>> semihosting breakpoint raises this exception which is then handled in
>> the usual exception processing path.
> 
> It's not fully internal though. Someone running with the `-d int`
> command line argument will see these exceptions, which don't
> correspond to anything in the spec.
> 
> Is there some way we could at least convey that information to users?

This is no different to EXCP_DEBUG, really, which is also internal to qemu but
user-visible in the same way.  Just adjust the logging in riscv_cpu_do_interrupt.


r~
Zhijian Li (Fujitsu)" via Oct. 26, 2020, 9:06 p.m. UTC | #6
Richard Henderson <richard.henderson@linaro.org> writes:

> This is no different to EXCP_DEBUG, really, which is also internal to qemu but
> user-visible in the same way.  Just adjust the logging in riscv_cpu_do_interrupt.

I think that's already handled by the early return in
riscv_cpu_do_interrupt after handling the RISCV_EXCP_SEMIHOST case.
Zhijian Li (Fujitsu)" via Oct. 26, 2020, 9:20 p.m. UTC | #7
Richard Henderson <richard.henderson@linaro.org> writes:

> On 10/23/20 2:49 PM, Keith Packard via wrote:
>>  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);
>
> Alistair asked if this approach is ok.  I think it is.  There are other places
> in which we scan forward (usually only forward, not backward, but this is a
> special nop, so it doesn't matter).
>
> However:
>
> (1) No need to re-read the current ebreak insn.  That is how we arrived here,
> after all.

The semihosting spec requires that we land here using a 32-bit ebreak
instruction, not the compressed version, so I think we still need to
check for this.

> (2) You need to check for page boundaries before reading pre and post.
> Otherwise you could wind up with SIGSEGV (or the equivalent internal qemu
> exception) when you shouldn't.

Right, in that case the ebreak instruction should just raise a regular
break point exception. I've added a check to make sure the address of
the three functions match when and-ed with TARGET_PAGE_MASK.

I've finished re-working this patch into a series which first modifies
the ARM semihosting support to be architecture-independent, then adds
the changes necessary to support RISC-V. Expect to see that series on
the list shortly.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 6a197bd358..8cf5b1e448 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -279,6 +279,7 @@  M: Palmer Dabbelt <palmer@dabbelt.com>
 M: Alistair Francis <Alistair.Francis@wdc.com>
 M: Sagar Karandikar <sagark@eecs.berkeley.edu>
 M: Bastian Koppelmann <kbastian@mail.uni-paderborn.de>
+M: Keith Packard <keithp@keithp.com>
 L: qemu-riscv@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"
+
 #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
 #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
+ */
+
+#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;
+
+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;
 } 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);
+}
+
 /* 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)